diff --git a/examples/organizations.csv b/examples/organizations.csv
index 61f0f82..2f447ef 100644
--- a/examples/organizations.csv
+++ b/examples/organizations.csv
@@ -1,2 +1,5 @@
-"ExO";"https://verify.exo.cat"
-"Somos Connexión";"https://verify.somosconexion.coop"
+"pangea.org";"https://idhub1.demo.pangea.org/oidc4vp/"
+"somconnexio.coop";"https://idhub2.demo.pangea.org/oidc4vp/"
+"exo.cat";"https://verify.exo.cat"
+"local 9000";"http://localhost:9000/oidc4vp/"
+"local 8000";"http://localhost:8000/oidc4vp/"
diff --git a/idhub/admin/forms.py b/idhub/admin/forms.py
index 4682579..2649f4b 100644
--- a/idhub/admin/forms.py
+++ b/idhub/admin/forms.py
@@ -19,9 +19,9 @@ from idhub_auth.models import User
class ImportForm(forms.Form):
- did = forms.ChoiceField(choices=[])
- schema = forms.ChoiceField(choices=[])
- file_import = forms.FileField()
+ did = forms.ChoiceField(label=_("Did"), choices=[])
+ schema = forms.ChoiceField(label=_("Schema"), choices=[])
+ file_import = forms.FileField(label=_("File import"))
def __init__(self, *args, **kwargs):
self._schema = None
@@ -139,7 +139,7 @@ class ImportForm(forms.Form):
class SchemaForm(forms.Form):
- file_template = forms.FileField()
+ file_template = forms.FileField(label=_("File template"))
class MembershipForm(forms.ModelForm):
diff --git a/idhub/admin/tables.py b/idhub/admin/tables.py
index 7ce7d43..79c22ba 100644
--- a/idhub/admin/tables.py
+++ b/idhub/admin/tables.py
@@ -1,4 +1,5 @@
import django_tables2 as tables
+from django.utils.translation import gettext_lazy as _
from idhub.models import Rol, Event
from idhub_auth.models import User
@@ -18,10 +19,6 @@ class RolesTable(tables.Table):
class DashboardTable(tables.Table):
- type = tables.Column(verbose_name="Event")
- message = tables.Column(verbose_name="Description")
- created = tables.Column(verbose_name="Date")
-
class Meta:
model = Event
template_name = "idhub/custom_table.html"
diff --git a/idhub/admin/views.py b/idhub/admin/views.py
index f8fd6d0..b6dcbc8 100644
--- a/idhub/admin/views.py
+++ b/idhub/admin/views.py
@@ -629,7 +629,7 @@ class DidsView(Credentials):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
- 'dids': DID.objects,
+ 'dids': DID.objects.filter(user=self.request.user),
})
return context
@@ -772,11 +772,14 @@ class SchemasNewView(SchemasMix):
return
try:
data = f.read().decode('utf-8')
- assert credtools.validate_schema(json.loads(data))
+ ldata = json.loads(data)
+ assert credtools.validate_schema(ldata)
+ name = ldata.get('name')
+ assert name
except Exception:
messages.error(self.request, _('This is not a valid schema!'))
return
- schema = Schemas.objects.create(file_schema=file_name, data=data)
+ schema = Schemas.objects.create(file_schema=file_name, data=data, type=name)
schema.save()
return schema
@@ -817,11 +820,14 @@ class SchemasImportAddView(SchemasMix):
def create_schema(self, file_name):
data = self.open_file(file_name)
try:
- json.loads(data)
+ ldata = json.loads(data)
+ assert credtools.validate_schema(ldata)
+ name = ldata.get('name')
+ assert name
except Exception:
messages.error(self.request, _('This is not a valid schema!'))
return
- schema = Schemas.objects.create(file_schema=file_name, data=data)
+ schema = Schemas.objects.create(file_schema=file_name, data=data, type=name)
schema.save()
return schema
diff --git a/idhub/email/views.py b/idhub/email/views.py
index 83d10cc..72e0daa 100644
--- a/idhub/email/views.py
+++ b/idhub/email/views.py
@@ -1,3 +1,5 @@
+import logging
+
from django.conf import settings
from django.template import loader
from django.core.mail import EmailMultiAlternatives
@@ -7,6 +9,9 @@ from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
+logger = logging.getLogger(__name__)
+
+
class NotifyActivateUserByEmail:
def get_email_context(self, user):
"""
@@ -49,10 +54,11 @@ class NotifyActivateUserByEmail:
email_message.attach_alternative(html_email, 'text/html')
try:
if settings.DEVELOPMENT:
- print(to_email)
- print(body)
+ logger.warning(to_email)
+ logger.warning(body)
return
email_message.send()
- except Exception:
+ except Exception as err:
+ logger.error(err)
return
diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py
index fd644e9..62c048a 100644
--- a/idhub/management/commands/initial_datas.py
+++ b/idhub/management/commands/initial_datas.py
@@ -1,11 +1,16 @@
import os
import csv
+import json
from pathlib import Path
+from utils import credtools
+from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth import get_user_model
from decouple import config
-from idhub.models import Organization
+from idhub.models import DID, Schemas
+from oidc4vp.models import Organization
+from promotion.models import Promotion
User = get_user_model()
@@ -29,6 +34,10 @@ class Command(BaseCommand):
f = csv.reader(csvfile, delimiter=';', quotechar='"')
for r in f:
self.create_organizations(r[0].strip(), r[1].strip())
+ self.sync_credentials_organizations("pangea.org", "somconnexio.coop")
+ self.sync_credentials_organizations("local 8000", "local 9000")
+ self.create_defaults_dids()
+ self.create_schemas()
def create_admin_users(self, email, password):
su = User.objects.create_superuser(email=email, password=password)
@@ -44,4 +53,48 @@ class Command(BaseCommand):
def create_organizations(self, name, url):
- Organization.objects.create(name=name, url=url)
+ Organization.objects.create(name=name, response_uri=url)
+
+ def sync_credentials_organizations(self, test1, test2):
+ org1 = Organization.objects.get(name=test1)
+ org2 = Organization.objects.get(name=test2)
+ org2.my_client_id = org1.client_id
+ org2.my_client_secret = org1.client_secret
+ org1.my_client_id = org2.client_id
+ org1.my_client_secret = org2.client_secret
+ org1.save()
+ org2.save()
+
+ def create_defaults_dids(self):
+ for u in User.objects.all():
+ did = DID(label="Default", user=u)
+ did.set_did()
+ did.save()
+
+ def create_schemas(self):
+ schemas_files = os.listdir(settings.SCHEMAS_DIR)
+ schemas = [x for x in schemas_files
+ if not Schemas.objects.filter(file_schema=x).exists()]
+ for x in schemas_files:
+ if Schemas.objects.filter(file_schema=x).exists():
+ continue
+ self._create_schemas(x)
+
+ def _create_schemas(self, file_name):
+ data = self.open_file(file_name)
+ try:
+ ldata = json.loads(data)
+ assert credtools.validate_schema(ldata)
+ name = ldata.get('name')
+ assert name
+ except Exception:
+ return
+ Schemas.objects.create(file_schema=file_name, data=data, type=name)
+
+ def open_file(self, file_name):
+ data = ''
+ filename = Path(settings.SCHEMAS_DIR).joinpath(file_name)
+ with filename.open() as schema_file:
+ data = schema_file.read()
+
+ return data
diff --git a/idhub/migrations/0001_initial.py b/idhub/migrations/0001_initial.py
index 5bd4f31..751af85 100644
--- a/idhub/migrations/0001_initial.py
+++ b/idhub/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.2.5 on 2024-01-04 15:12
+# Generated by Django 4.2.5 on 2024-01-04 16:59
from django.conf import settings
from django.db import migrations, models
@@ -26,7 +26,7 @@ class Migration(migrations.Migration):
),
),
('created_at', models.DateTimeField(auto_now=True)),
- ('label', models.CharField(max_length=50)),
+ ('label', models.CharField(max_length=50, verbose_name='Label')),
('did', models.CharField(max_length=250)),
('_key_material', models.BinaryField(max_length=250)),
(
@@ -57,27 +57,6 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now=True)),
],
),
- migrations.CreateModel(
- name='Organization',
- fields=[
- (
- 'id',
- models.BigAutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name='ID',
- ),
- ),
- ('name', models.CharField(max_length=250)),
- (
- 'url',
- models.CharField(
- help_text='Url where to send the presentation', max_length=250
- ),
- ),
- ],
- ),
migrations.CreateModel(
name='Rol',
fields=[
@@ -111,6 +90,7 @@ class Migration(migrations.Migration):
verbose_name='ID',
),
),
+ ('type', models.CharField(max_length=250)),
('file_schema', models.CharField(max_length=250)),
('data', models.TextField()),
('created_at', models.DateTimeField(auto_now=True)),
@@ -168,8 +148,7 @@ class Migration(migrations.Migration):
('verified', models.BooleanField()),
('created_on', models.DateTimeField(auto_now=True)),
('issued_on', models.DateTimeField(null=True)),
- ('subject_did', models.CharField(max_length=250)),
- ('_data', models.BinaryField()),
+ ('data', models.TextField()),
('csv_data', models.TextField()),
(
'status',
@@ -199,6 +178,15 @@ class Migration(migrations.Migration):
to='idhub.schemas',
),
),
+ (
+ 'subject_did',
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='subject_credentials',
+ to='idhub.did',
+ ),
+ ),
(
'user',
models.ForeignKey(
@@ -268,8 +256,11 @@ class Migration(migrations.Migration):
verbose_name='ID',
),
),
- ('created', models.DateTimeField(auto_now=True)),
- ('message', models.CharField(max_length=350)),
+ ('created', models.DateTimeField(auto_now=True, verbose_name='Date')),
+ (
+ 'message',
+ models.CharField(max_length=350, verbose_name='Description'),
+ ),
(
'type',
models.PositiveSmallIntegerField(
@@ -307,7 +298,8 @@ class Migration(migrations.Migration):
(28, 'Organisational DID deleted by admin'),
(29, 'User deactivated'),
(30, 'User activated'),
- ]
+ ],
+ verbose_name='Event',
),
),
(
@@ -339,6 +331,7 @@ class Migration(migrations.Migration):
on_delete=django.db.models.deletion.CASCADE,
related_name='users',
to='idhub.service',
+ verbose_name='Service',
),
),
(
diff --git a/idhub/models.py b/idhub/models.py
index 05b163a..30ad551 100644
--- a/idhub/models.py
+++ b/idhub/models.py
@@ -1,6 +1,5 @@
import json
import pytz
-import requests
import datetime
from django.db import models
from django.conf import settings
@@ -50,9 +49,10 @@ class Event(models.Model):
EV_USR_DEACTIVATED_BY_ADMIN = 29, "User deactivated"
EV_USR_ACTIVATED_BY_ADMIN = 30, "User activated"
- created = models.DateTimeField(auto_now=True)
- message = models.CharField(max_length=350)
+ created = models.DateTimeField(_("Date"), auto_now=True)
+ message = models.CharField(_("Description"), max_length=350)
type = models.PositiveSmallIntegerField(
+ _("Event"),
choices=Types.choices,
)
user = models.ForeignKey(
@@ -407,7 +407,7 @@ class Event(models.Model):
class DID(models.Model):
created_at = models.DateTimeField(auto_now=True)
- label = models.CharField(max_length=50)
+ label = models.CharField(_("Label"), max_length=50)
did = models.CharField(max_length=250)
# In JWK format. Must be stored as-is and passed whole to library functions.
# Example key material:
@@ -459,6 +459,7 @@ class DID(models.Model):
class Schemas(models.Model):
+ type = models.CharField(max_length=250)
file_schema = models.CharField(max_length=250)
data = models.TextField()
created_at = models.DateTimeField(auto_now=True)
@@ -490,11 +491,7 @@ class VerificableCredential(models.Model):
verified = models.BooleanField()
created_on = models.DateTimeField(auto_now=True)
issued_on = models.DateTimeField(null=True)
- subject_did = models.CharField(max_length=250)
- # CHANGED: `data` to `_data`, datatype from TextField to BinaryField and the rendered VC is now stored encrypted.
- # TODO: verify that BinaryField can hold arbitrary amounts of data (max_length = ???)
- data = None
- _data = models.BinaryField()
+ data = models.TextField()
csv_data = models.TextField()
status = models.PositiveSmallIntegerField(
choices=Status.choices,
@@ -505,6 +502,12 @@ class VerificableCredential(models.Model):
on_delete=models.CASCADE,
related_name='vcredentials',
)
+ subject_did = models.ForeignKey(
+ DID,
+ on_delete=models.CASCADE,
+ related_name='subject_credentials',
+ null=True
+ )
issuer_did = models.ForeignKey(
DID,
on_delete=models.CASCADE,
@@ -517,36 +520,16 @@ class VerificableCredential(models.Model):
)
def get_data(self):
- key_dids = cache.get("KEY_DIDS", {})
- if not key_dids.get(user.id):
- raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.")
- sb = secret.SecretBox(key_dids[user.id])
- return sb.decrypt(self._data)
+ return self.user.decrypt_data(self.data)
def set_data(self, value):
- key_dids = cache.get("KEY_DIDS", {})
- if not key_dids.get(user.id):
- raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.")
- sb = secret.SecretBox(key_dids[user.id])
- self._data = sb.encrypt(value)
-
- @property
- def get_schema(self):
- if not self.data:
- return {}
- return json.loads(self.data)
+ self.data = self.user.encrypt_data(value)
def type(self):
- if self.data:
- return self.get_schema.get('type')[-1]
-
- return self.schema.name()
+ return self.schema.type
def description(self):
- if not self.data:
- return self.schema.description()
-
- for des in self.get_schema.get('description', []):
+ for des in json.loads(self.render()).get('description', []):
if settings.LANGUAGE_CODE == des.get('lang'):
return des.get('value', '')
return ''
@@ -572,13 +555,15 @@ class VerificableCredential(models.Model):
def get_context(self):
d = json.loads(self.csv_data)
- format = "%Y-%m-%dT%H:%M:%SZ"
- issuance_date = self.issued_on.strftime(format)
+ issuance_date = ''
+ if self.issued_on:
+ format = "%Y-%m-%dT%H:%M:%SZ"
+ issuance_date = self.issued_on.strftime(format)
context = {
'vc_id': self.id,
'issuer_did': self.issuer_did.did,
- 'subject_did': self.subject_did,
+ 'subject_did': self.subject_did and self.subject_did.did or '',
'issuance_date': issuance_date,
'first_name': self.user.first_name,
'last_name': self.user.last_name,
@@ -677,24 +662,10 @@ class UserRol(models.Model):
)
service = models.ForeignKey(
Service,
+ verbose_name=_("Service"),
on_delete=models.CASCADE,
related_name='users',
)
class Meta:
unique_together = ('user', 'service',)
-
-
-class Organization(models.Model):
- name = models.CharField(max_length=250)
- url = models.CharField(
- help_text=_("Url where to send the presentation"),
- max_length=250
- )
-
- def __str__(self):
- return self.name
-
- def send(self, cred):
- return
- requests.post(self.url, data=cred.data)
diff --git a/idhub/templates/credentials/exo.json b/idhub/templates/credentials/exo.json
new file mode 100644
index 0000000..1aee10b
--- /dev/null
+++ b/idhub/templates/credentials/exo.json
@@ -0,0 +1,30 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ {
+ "name": "https://schema.org/name",
+ "email": "https://schema.org/email",
+ "membershipType": "https://schema.org/memberOf",
+ "individual": "https://schema.org/Person",
+ "organization": "https://schema.org/Organization",
+ "Member": "https://schema.org/Member",
+ "startDate": "https://schema.org/startDate",
+ "jsonSchema": "https://schema.org/jsonSchema",
+ "street_address": "https://schema.org/streetAddress",
+ "connectivity_option_list": "https://schema.org/connectivityOptionList",
+ "$ref": "https://schema.org/jsonSchemaRef"
+ }
+ ],
+ "id": "{{ vc_id }}",
+ "type": ["VerifiableCredential", "HomeConnectivitySurveyCredential"],
+ "issuer": "{{ issuer_did }}",
+ "issuanceDate": "{{ issuance_date }}",
+ "credentialSubject": {
+ "id": "{{ subject_did }}",
+ "street_address": "{{ street_address }}",
+ "connectivity_option_list": "{{ connectivity_option_list }}",
+ "jsonSchema": {
+ "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json"
+ }
+ }
+}
diff --git a/idhub/templates/credentials/openarms.json b/idhub/templates/credentials/openarms.json
new file mode 100644
index 0000000..e2ecbc6
--- /dev/null
+++ b/idhub/templates/credentials/openarms.json
@@ -0,0 +1,33 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ {
+ "name": "https://schema.org/name",
+ "email": "https://schema.org/email",
+ "membershipType": "https://schema.org/memberOf",
+ "individual": "https://schema.org/Person",
+ "organization": "https://schema.org/Organization",
+ "Member": "https://schema.org/Member",
+ "startDate": "https://schema.org/startDate",
+ "jsonSchema": "https://schema.org/jsonSchema",
+ "destination_country": "https://schema.org/destinationCountry",
+ "offboarding_date": "https://schema.org/offboardingDate",
+ "$ref": "https://schema.org/jsonSchemaRef"
+ }
+ ],
+ "id": "{{ vc_id }}",
+ "type": ["VerifiableCredential", "MigrantRescueCredential"],
+ "issuer": "{{ issuer_did }}",
+ "issuanceDate": "{{ issuance_date }}",
+ "credentialSubject": {
+ "id": "{{ subject_did }}",
+ "name": "{{ name }}",
+ "country_of_origin": "{{ country_of_origin }}",
+ "rescue_date": "{{ rescue_date }}",
+ "destination_country": "{{ destination_country }}",
+ "offboarding_date": "{{ offboarding_date }}",
+ "jsonSchema": {
+ "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json"
+ }
+ }
+}
diff --git a/idhub/templates/credentials/paremanel.json b/idhub/templates/credentials/paremanel.json
new file mode 100644
index 0000000..a40396b
--- /dev/null
+++ b/idhub/templates/credentials/paremanel.json
@@ -0,0 +1,30 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ {
+ "name": "https://schema.org/name",
+ "email": "https://schema.org/email",
+ "membershipType": "https://schema.org/memberOf",
+ "individual": "https://schema.org/Person",
+ "organization": "https://schema.org/Organization",
+ "Member": "https://schema.org/Member",
+ "startDate": "https://schema.org/startDate",
+ "jsonSchema": "https://schema.org/jsonSchema",
+ "street_address": "https://schema.org/streetAddress",
+ "financial_vulnerability_score": "https://schema.org/financialVulnerabilityScore",
+ "$ref": "https://schema.org/jsonSchemaRef"
+ }
+ ],
+ "id": "{{ vc_id }}",
+ "type": ["VerifiableCredential", "FinancialSituationCredential"],
+ "issuer": "{{ issuer_did }}",
+ "issuanceDate": "{{ issuance_date }}",
+ "credentialSubject": {
+ "id": "{{ subject_did }}",
+ "street_address": "{{ street_address }}",
+ "financial_vulnerability_score": "{{ financial_vulnerability_score }}",
+ "jsonSchema": {
+ "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json"
+ }
+ }
+}
diff --git a/idhub/templates/idhub/admin/roles.html b/idhub/templates/idhub/admin/roles.html
index 25bfd93..d3d6593 100644
--- a/idhub/templates/idhub/admin/roles.html
+++ b/idhub/templates/idhub/admin/roles.html
@@ -24,7 +24,7 @@
{{ rol.name }}
{{ rol.description|default:""}}
-
+
{% endfor %}
diff --git a/idhub/templates/idhub/admin/services.html b/idhub/templates/idhub/admin/services.html
index cea981d..639d1c1 100644
--- a/idhub/templates/idhub/admin/services.html
+++ b/idhub/templates/idhub/admin/services.html
@@ -26,7 +26,7 @@
{{ service.description }}
{{ service.get_roles }}
-
+
{% endfor %}
diff --git a/idhub/templates/idhub/admin/user.html b/idhub/templates/idhub/admin/user.html
index aacd509..fbb04d1 100644
--- a/idhub/templates/idhub/admin/user.html
+++ b/idhub/templates/idhub/admin/user.html
@@ -21,7 +21,7 @@
- First Name:
+ {% trans "First name" %}:
{{ object.first_name|default:'' }}
@@ -29,7 +29,7 @@
- Last Name:
+ {% trans "Last name" %}:
{{ object.last_name|default:'' }}
@@ -37,7 +37,7 @@
- Email:
+ {% trans "Email address" %}:
{{ object.email }}
diff --git a/idhub/templates/idhub/admin/user_edit.html b/idhub/templates/idhub/admin/user_edit.html
index 9bc0a48..5198cca 100644
--- a/idhub/templates/idhub/admin/user_edit.html
+++ b/idhub/templates/idhub/admin/user_edit.html
@@ -52,7 +52,7 @@
{{ membership.start_date|default:'' }}
{{ membership.end_date|default:'' }}
-
+
{% endfor %}
@@ -84,7 +84,7 @@
{{ rol.service.description }}
{{ rol.service.domain }}
-
+
{% endfor %}
diff --git a/idhub/templates/idhub/base.html b/idhub/templates/idhub/base.html
index c8d985b..390cb33 100644
--- a/idhub/templates/idhub/base.html
+++ b/idhub/templates/idhub/base.html
@@ -115,7 +115,7 @@
-
+
{% trans 'Present a credential' %}
@@ -139,7 +139,7 @@
{{ title }}
diff --git a/idhub/templates/idhub/base_admin.html b/idhub/templates/idhub/base_admin.html
index 1253806..c97f165 100644
--- a/idhub/templates/idhub/base_admin.html
+++ b/idhub/templates/idhub/base_admin.html
@@ -171,7 +171,7 @@
{{ title }}
diff --git a/idhub/urls.py b/idhub/urls.py
index f7df70d..69acc22 100644
--- a/idhub/urls.py
+++ b/idhub/urls.py
@@ -20,6 +20,7 @@ from django.urls import path, reverse_lazy
from .views import LoginView, PasswordResetConfirmView
from .admin import views as views_admin
from .user import views as views_user
+# from .verification_portal import views as views_verification_portal
app_name = 'idhub'
@@ -87,9 +88,9 @@ urlpatterns = [
path('user/credentials/request/',
views_user.CredentialsRequestView.as_view(),
name='user_credentials_request'),
- path('user/credentials_presentation/',
- views_user.CredentialsPresentationView.as_view(),
- name='user_credentials_presentation'),
+ path('user/credentials_presentation/demand',
+ views_user.DemandAuthorizationView.as_view(),
+ name='user_demand_authorization'),
# Admin
path('admin/dashboard/', views_admin.DashboardView.as_view(),
@@ -174,4 +175,7 @@ urlpatterns = [
name='admin_import'),
path('admin/import/new', views_admin.ImportAddView.as_view(),
name='admin_import_add'),
+
+ # path('verification_portal/verify/', views_verification_portal.verify,
+ # name="verification_portal_verify")
]
diff --git a/idhub/user/forms.py b/idhub/user/forms.py
index 53a1149..5ac04ad 100644
--- a/idhub/user/forms.py
+++ b/idhub/user/forms.py
@@ -1,7 +1,11 @@
+import requests
from django import forms
-from idhub_auth.models import User
-from idhub.models import DID, VerificableCredential, Organization
+from django.conf import settings
+from django.utils.translation import gettext_lazy as _
+from idhub_auth.models import User
+from idhub.models import DID, VerificableCredential
+from oidc4vp.models import Organization
class ProfileForm(forms.ModelForm):
@@ -13,8 +17,8 @@ class ProfileForm(forms.ModelForm):
class RequestCredentialForm(forms.Form):
- did = forms.ChoiceField(choices=[])
- credential = forms.ChoiceField(choices=[])
+ did = forms.ChoiceField(label=_("Did"), choices=[])
+ credential = forms.ChoiceField(label=_("Credential"), choices=[])
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
@@ -42,7 +46,7 @@ class RequestCredentialForm(forms.Form):
if not all([cred.exists(), did.exists()]):
return
- did = did[0].did
+ did = did[0]
cred = cred[0]
try:
cred.issue(did)
@@ -56,42 +60,30 @@ class RequestCredentialForm(forms.Form):
return
-
-class CredentialPresentationForm(forms.Form):
- organization = forms.ChoiceField(choices=[])
- credential = forms.ChoiceField(choices=[])
+class DemandAuthorizationForm(forms.Form):
+ organization = forms.ChoiceField(label=_("Organization"), choices=[])
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
self.fields['organization'].choices = [
- (x.id, x.name) for x in Organization.objects.filter()
- ]
- self.fields['credential'].choices = [
- (x.id, x.type()) for x in VerificableCredential.objects.filter(
- user=self.user,
- status=VerificableCredential.Status.ISSUED
- )
+ (x.id, x.name) for x in Organization.objects.filter()
+ if x.response_uri != settings.RESPONSE_URI
]
def save(self, commit=True):
self.org = Organization.objects.filter(
id=self.data['organization']
)
- self.cred = VerificableCredential.objects.filter(
- user=self.user,
- id=self.data['credential'],
- status=VerificableCredential.Status.ISSUED
- )
- if not all([self.org.exists(), self.cred.exists()]):
+ if not self.org.exists():
return
self.org = self.org[0]
- self.cred = self.cred[0]
if commit:
- self.org.send(self.cred)
- return self.cred
+ url = self.org.demand_authorization()
+ if url.status_code == 200:
+ return url.json().get('redirect_uri')
return
diff --git a/idhub/user/views.py b/idhub/user/views.py
index 482b40e..e6e28dc 100644
--- a/idhub/user/views.py
+++ b/idhub/user/views.py
@@ -12,7 +12,11 @@ from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.http import HttpResponse
from django.contrib import messages
-from idhub.user.forms import ProfileForm, RequestCredentialForm, CredentialPresentationForm
+from idhub.user.forms import (
+ ProfileForm,
+ RequestCredentialForm,
+ DemandAuthorizationForm
+)
from idhub.mixins import UserView
from idhub.models import DID, VerificableCredential, Event
@@ -76,8 +80,11 @@ class CredentialsView(MyWallet, TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
+ creds = VerificableCredential.objects.filter(
+ user=self.request.user
+ )
context.update({
- 'credentials': VerificableCredential.objects,
+ 'credentials': creds,
})
return context
@@ -136,17 +143,20 @@ class CredentialsRequestView(MyWallet, FormView):
messages.success(self.request, _("The credential was issued successfully!"))
Event.set_EV_CREDENTIAL_ISSUED_FOR_USER(cred)
Event.set_EV_CREDENTIAL_ISSUED(cred)
+ url = self.request.session.pop('next_url', None)
+ if url:
+ return redirect(url)
else:
messages.error(self.request, _("The credential does not exist!"))
return super().form_valid(form)
-class CredentialsPresentationView(MyWallet, FormView):
+class DemandAuthorizationView(MyWallet, FormView):
template_name = "idhub/user/credentials_presentation.html"
subtitle = _('Credential presentation')
icon = 'bi bi-patch-check-fill'
- form_class = CredentialPresentationForm
- success_url = reverse_lazy('idhub:user_credentials')
+ form_class = DemandAuthorizationForm
+ success_url = reverse_lazy('idhub:user_demand_authorization')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
@@ -154,11 +164,17 @@ class CredentialsPresentationView(MyWallet, FormView):
return kwargs
def form_valid(self, form):
- cred = form.save()
- if cred:
- Event.set_EV_CREDENTIAL_PRESENTED_BY_USER(cred, form.org)
- Event.set_EV_CREDENTIAL_PRESENTED(cred, form.org)
- messages.success(self.request, _("The credential was presented successfully!"))
+ try:
+ authorization = form.save()
+ except Exception:
+ txt = _("Problems connecting with {url}").format(
+ url=form.org.response_uri
+ )
+ messages.error(self.request, txt)
+ return super().form_valid(form)
+
+ if authorization:
+ return redirect(authorization)
else:
messages.error(self.request, _("Error sending credential!"))
return super().form_valid(form)
diff --git a/idhub/verification_portal/__init__.py b/idhub/verification_portal/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/idhub/verification_portal/models.py b/idhub/verification_portal/models.py
new file mode 100644
index 0000000..0bd203a
--- /dev/null
+++ b/idhub/verification_portal/models.py
@@ -0,0 +1,24 @@
+from django.db import models
+
+
+class VPVerifyRequest(models.Model):
+ """
+ `nonce` is an opaque random string used to lookup verification requests. URL-safe.
+ Example: "UPBQ3JE2DGJYHP5CPSCRIGTHRTCYXMQPNQ"
+ `expected_credentials` is a JSON list of credential types that must be present in this VP.
+ Example: ["FinancialSituationCredential", "HomeConnectivitySurveyCredential"]
+ `expected_contents` is a JSON object that places optional constraints on the contents of the
+ returned VP.
+ Example: [{"FinancialSituationCredential": {"financial_vulnerability_score": "7"}}]
+ `action` is (for now) a JSON object describing the next steps to take if this verification
+ is successful. For example "send mail to
with and "
+ Example: {"action": "send_mail", "params": {"to": "orders@somconnexio.coop", "subject": "New client", "body": ...}
+ `response` is a URL that the user's wallet will redirect the user to.
+ `submitted_on` is used (by a cronjob) to purge old entries that didn't complete verification
+ """
+ nonce = models.CharField(max_length=50)
+ expected_credentials = models.CharField(max_length=255)
+ expected_contents = models.TextField()
+ action = models.TextField()
+ response_or_redirect = models.CharField(max_length=255)
+ submitted_on = models.DateTimeField(auto_now=True)
diff --git a/idhub/verification_portal/views.py b/idhub/verification_portal/views.py
new file mode 100644
index 0000000..486f4f7
--- /dev/null
+++ b/idhub/verification_portal/views.py
@@ -0,0 +1,49 @@
+import json
+
+from django.core.mail import send_mail
+from django.http import HttpResponse, HttpResponseRedirect
+
+from utils.idhub_ssikit import verify_presentation
+from .models import VPVerifyRequest
+from django.shortcuts import get_object_or_404
+from more_itertools import flatten, unique_everseen
+
+
+def verify(request):
+ assert request.method == "POST"
+ # TODO: incorporate request.POST["presentation_submission"] as schema definition
+ (presentation_valid, _) = verify_presentation(request.POST["vp_token"])
+ if not presentation_valid:
+ raise Exception("Failed to verify signature on the given Verifiable Presentation.")
+ vp = json.loads(request.POST["vp_token"])
+ nonce = vp["nonce"]
+ # "vr" = verification_request
+ vr = get_object_or_404(VPVerifyRequest, nonce=nonce) # TODO: return meaningful error, not 404
+ # Get a list of all included verifiable credential types
+ included_credential_types = unique_everseen(flatten([
+ vc["type"] for vc in vp["verifiableCredential"]
+ ]))
+ # Check that it matches what we requested
+ for requested_vc_type in json.loads(vr.expected_credentials):
+ if requested_vc_type not in included_credential_types:
+ raise Exception("You're missing some credentials we requested!") # TODO: return meaningful error
+ # Perform whatever action we have to do
+ action = json.loads(vr.action)
+ if action["action"] == "send_mail":
+ subject = action["params"]["subject"]
+ to_email = action["params"]["to"]
+ from_email = "noreply@verifier-portal"
+ body = request.POST["vp-token"]
+ send_mail(
+ subject,
+ body,
+ from_email,
+ [to_email]
+ )
+ elif action["action"] == "something-else":
+ pass
+ else:
+ raise Exception("Unknown action!")
+ # OK! Your verifiable presentation was successfully presented.
+ return HttpResponseRedirect(vr.response_or_redirect)
+
diff --git a/idhub_auth/forms.py b/idhub_auth/forms.py
index fa771d4..d9ff2f7 100644
--- a/idhub_auth/forms.py
+++ b/idhub_auth/forms.py
@@ -16,7 +16,7 @@ class ProfileForm(forms.ModelForm):
def clean_first_name(self):
first_name = super().clean()['first_name']
match = r'^[a-zA-ZäöüßÄÖÜáéíóúàèìòùÀÈÌÒÙÁÉÍÓÚßñÑçÇ\s\'-]+'
- if not re.match(match, first_name):
+ if not re.fullmatch(match, first_name):
txt = _("The string must contain only characters and spaces")
raise forms.ValidationError(txt)
@@ -25,7 +25,7 @@ class ProfileForm(forms.ModelForm):
def clean_last_name(self):
last_name = super().clean()['last_name']
match = r'^[a-zA-ZäöüßÄÖÜáéíóúàèìòùÀÈÌÒÙÁÉÍÓÚßñÑçÇ\s\'-]+'
- if not re.match(match, last_name):
+ if not re.fullmatch(match, last_name):
txt = _("The string must contain only characters and spaces")
raise forms.ValidationError(txt)
diff --git a/idhub_auth/migrations/0001_initial.py b/idhub_auth/migrations/0001_initial.py
index 5655a6f..8ea6578 100644
--- a/idhub_auth/migrations/0001_initial.py
+++ b/idhub_auth/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.2.5 on 2024-01-04 15:12
+# Generated by Django 4.2.5 on 2024-01-04 16:59
from django.db import migrations, models
@@ -31,13 +31,23 @@ class Migration(migrations.Migration):
(
'email',
models.EmailField(
- max_length=255, unique=True, verbose_name='email address'
+ max_length=255, unique=True, verbose_name='Email address'
),
),
('is_active', models.BooleanField(default=True)),
('is_admin', models.BooleanField(default=False)),
- ('first_name', models.CharField(blank=True, max_length=255, null=True)),
- ('last_name', models.CharField(blank=True, max_length=255, null=True)),
+ (
+ 'first_name',
+ models.CharField(
+ blank=True, max_length=255, null=True, verbose_name='First name'
+ ),
+ ),
+ (
+ 'last_name',
+ models.CharField(
+ blank=True, max_length=255, null=True, verbose_name='Last name'
+ ),
+ ),
('encrypted_sensitive_data', models.CharField(max_length=255)),
('salt', models.CharField(max_length=255)),
],
diff --git a/idhub_auth/models.py b/idhub_auth/models.py
index 7c3434d..aed2199 100644
--- a/idhub_auth/models.py
+++ b/idhub_auth/models.py
@@ -4,6 +4,7 @@ import base64
from nacl import pwhash
from django.db import models
from django.core.cache import cache
+from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
@@ -40,14 +41,14 @@ class UserManager(BaseUserManager):
class User(AbstractBaseUser):
email = models.EmailField(
- verbose_name="email address",
+ _('Email address'),
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
- first_name = models.CharField(max_length=255, blank=True, null=True)
- last_name = models.CharField(max_length=255, blank=True, null=True)
+ first_name = models.CharField(_("First name"), max_length=255, blank=True, null=True)
+ last_name = models.CharField(_("Last name"), max_length=255, blank=True, null=True)
encrypted_sensitive_data = models.CharField(max_length=255)
salt = models.CharField(max_length=255)
@@ -144,4 +145,21 @@ class User(AbstractBaseUser):
key_crypted = self.encrypt_sensitive_data(password, key)
self.encrypted_sensitive_data = key_crypted
+ def encrypt_data(self, data):
+ sb = self.get_secret_box()
+ value = base64.b64encode(data.encode('utf-8'))
+ return sb.encrypt(data)
+ def decrypt_data(self, data):
+ sb = self.get_secret_box()
+ value = base64.b64decode(data.encode('utf-8'))
+ return sb.decrypt(data)
+
+ def get_secret_box(self):
+ key_dids = cache.get("KEY_DIDS", {})
+ if not key_dids.get(self.id):
+ err = "An attempt is made to access encrypted "
+ err += "data without having the key."
+ raise Exception(_(err))
+
+ return secret.SecretBox(key_dids[self.id])
diff --git a/locale/ca/LC_MESSAGES/django.mo b/locale/ca/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..965887f
Binary files /dev/null and b/locale/ca/LC_MESSAGES/django.mo differ
diff --git a/locale/ca_ES/LC_MESSAGES/django.po b/locale/ca/LC_MESSAGES/django.po
similarity index 72%
rename from locale/ca_ES/LC_MESSAGES/django.po
rename to locale/ca/LC_MESSAGES/django.po
index ff421ea..c8a7ede 100644
--- a/locale/ca_ES/LC_MESSAGES/django.po
+++ b/locale/ca/LC_MESSAGES/django.po
@@ -3,227 +3,231 @@
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
-#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
+"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-21 14:37+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"Language: \n"
+"PO-Revision-Date: 2023-11-27 20:37+0100\n"
+"Last-Translator: Leandro Navarro \n"
+"Language-Team: \n"
+"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.2.2\n"
#: env/lib/python3.11/site-packages/bootstrap4/components.py:17
#: env/lib/python3.11/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3
#: env/lib/python3.11/site-packages/bootstrap4/templates/bootstrap4/messages.html:4
#: env/lib/python3.11/site-packages/django_bootstrap5/components.py:26
msgid "close"
-msgstr ""
+msgstr "tanca"
#: env/lib/python3.11/site-packages/click/_termui_impl.py:518
#, python-brace-format
msgid "{editor}: Editing failed"
-msgstr ""
+msgstr "{editor}: Error en l'edició"
#: env/lib/python3.11/site-packages/click/_termui_impl.py:522
#, python-brace-format
msgid "{editor}: Editing failed: {e}"
-msgstr ""
+msgstr "{editor}: Error en l'edició: {e}"
#: env/lib/python3.11/site-packages/click/core.py:1120
msgid "Aborted!"
-msgstr ""
+msgstr "Interromput!"
#: env/lib/python3.11/site-packages/click/core.py:1309
#: env/lib/python3.11/site-packages/click/decorators.py:559
msgid "Show this message and exit."
-msgstr ""
+msgstr "Mostra aquest missatge i surt."
#: env/lib/python3.11/site-packages/click/core.py:1340
#: env/lib/python3.11/site-packages/click/core.py:1370
#, python-brace-format
msgid "(Deprecated) {text}"
-msgstr ""
+msgstr "(Obsolet) {text}"
#: env/lib/python3.11/site-packages/click/core.py:1387
msgid "Options"
-msgstr ""
+msgstr "Opcions"
#: env/lib/python3.11/site-packages/click/core.py:1413
#, python-brace-format
msgid "Got unexpected extra argument ({args})"
msgid_plural "Got unexpected extra arguments ({args})"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "S'ha trobat un argument addicional inesperat ({args})"
+msgstr[1] "S'han trobat uns arguments addicionals inesperats ({args})"
#: env/lib/python3.11/site-packages/click/core.py:1429
msgid "DeprecationWarning: The command {name!r} is deprecated."
-msgstr ""
+msgstr "Advertència obsolescència: l'ordre {name!r} està obsoleta."
#: env/lib/python3.11/site-packages/click/core.py:1636
msgid "Commands"
-msgstr ""
+msgstr "Ordres"
#: env/lib/python3.11/site-packages/click/core.py:1668
msgid "Missing command."
-msgstr ""
+msgstr "Falta l'ordre."
#: env/lib/python3.11/site-packages/click/core.py:1746
msgid "No such command {name!r}."
-msgstr ""
+msgstr "No hi ha aquesta comanda {name!r}."
#: env/lib/python3.11/site-packages/click/core.py:2310
msgid "Value must be an iterable."
-msgstr ""
+msgstr "El valor ha de ser iterable."
#: env/lib/python3.11/site-packages/click/core.py:2331
#, python-brace-format
msgid "Takes {nargs} values but 1 was given."
msgid_plural "Takes {nargs} values but {len} were given."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Pren {nargs} valors però s'ha donat 1."
+msgstr[1] "Pren {nargs} valors però s'han donat {len}."
#: env/lib/python3.11/site-packages/click/core.py:2778
#, python-brace-format
msgid "env var: {var}"
-msgstr ""
+msgstr "env var: {var}"
#: env/lib/python3.11/site-packages/click/core.py:2808
msgid "(dynamic)"
-msgstr ""
+msgstr "(dinàmic)"
#: env/lib/python3.11/site-packages/click/core.py:2821
#, python-brace-format
msgid "default: {default}"
-msgstr ""
+msgstr "per defecte: {default}"
#: env/lib/python3.11/site-packages/click/core.py:2834
msgid "required"
-msgstr ""
+msgstr "obligatori"
#: env/lib/python3.11/site-packages/click/decorators.py:465
#, python-format
msgid "%(prog)s, version %(version)s"
-msgstr ""
+msgstr "%(prog)s, versió %(version)s"
#: env/lib/python3.11/site-packages/click/decorators.py:528
msgid "Show the version and exit."
-msgstr ""
+msgstr "Mostra la versió i surt."
#: env/lib/python3.11/site-packages/click/exceptions.py:44
#: env/lib/python3.11/site-packages/click/exceptions.py:80
#, python-brace-format
msgid "Error: {message}"
-msgstr ""
+msgstr "Error: {message}"
#: env/lib/python3.11/site-packages/click/exceptions.py:72
#, python-brace-format
msgid "Try '{command} {option}' for help."
-msgstr ""
+msgstr "Proveu ‘{command} {option}’ per obtenir ajuda."
#: env/lib/python3.11/site-packages/click/exceptions.py:121
#, python-brace-format
msgid "Invalid value: {message}"
-msgstr ""
+msgstr "Valor no vàlid: {message}"
#: env/lib/python3.11/site-packages/click/exceptions.py:123
#, python-brace-format
msgid "Invalid value for {param_hint}: {message}"
-msgstr ""
+msgstr "Valor no vàlid per a {param_hint}: {message}"
#: env/lib/python3.11/site-packages/click/exceptions.py:179
msgid "Missing argument"
-msgstr ""
+msgstr "Manca l'argument"
#: env/lib/python3.11/site-packages/click/exceptions.py:181
msgid "Missing option"
-msgstr ""
+msgstr "Falta opció"
#: env/lib/python3.11/site-packages/click/exceptions.py:183
msgid "Missing parameter"
-msgstr ""
+msgstr "Falta un paràmetre"
#: env/lib/python3.11/site-packages/click/exceptions.py:185
#, python-brace-format
msgid "Missing {param_type}"
-msgstr ""
+msgstr "Falta {param_type}"
#: env/lib/python3.11/site-packages/click/exceptions.py:192
#, python-brace-format
msgid "Missing parameter: {param_name}"
-msgstr ""
+msgstr "Falta el paràmetre: {param_name}"
#: env/lib/python3.11/site-packages/click/exceptions.py:212
#, python-brace-format
msgid "No such option: {name}"
-msgstr ""
+msgstr "No hi ha aquesta opció: {name}"
#: env/lib/python3.11/site-packages/click/exceptions.py:224
#, python-brace-format
msgid "Did you mean {possibility}?"
msgid_plural "(Possible options: {possibilities})"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Voleu dir {possibility}?"
+msgstr[1] "(Opcions possibles: {possibilities})"
#: env/lib/python3.11/site-packages/click/exceptions.py:262
msgid "unknown error"
-msgstr ""
+msgstr "error desconegut"
#: env/lib/python3.11/site-packages/click/exceptions.py:269
msgid "Could not open file {filename!r}: {message}"
-msgstr ""
+msgstr "No s'ha pogut obrir el fitxer {filename!r}: {message}"
#: env/lib/python3.11/site-packages/click/parser.py:231
msgid "Argument {name!r} takes {nargs} values."
-msgstr ""
+msgstr "L'argument {name!r} pren {nargs} valors."
#: env/lib/python3.11/site-packages/click/parser.py:413
msgid "Option {name!r} does not take a value."
-msgstr ""
+msgstr "L'opció {name!r} no pren cap valor."
#: env/lib/python3.11/site-packages/click/parser.py:474
msgid "Option {name!r} requires an argument."
msgid_plural "Option {name!r} requires {nargs} arguments."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "L'opció {name!r} requereix un argument."
+msgstr[1] "L'opció {name!r} requereix {nargs} arguments."
#: env/lib/python3.11/site-packages/click/shell_completion.py:319
msgid "Shell completion is not supported for Bash versions older than 4.4."
msgstr ""
+"La finalització de l'intèrpret d'ordres no és compatible amb les versions de "
+"Bash anteriors a la 4.4."
#: env/lib/python3.11/site-packages/click/shell_completion.py:326
msgid "Couldn't detect Bash version, shell completion is not supported."
msgstr ""
+"No s'ha pogut detectar la versió de Bash, la finalització de l'intèrpret "
+"d'ordres no és compatible."
#: env/lib/python3.11/site-packages/click/termui.py:158
msgid "Repeat for confirmation"
-msgstr ""
+msgstr "Repetiu per confirmar"
#: env/lib/python3.11/site-packages/click/termui.py:174
msgid "Error: The value you entered was invalid."
-msgstr ""
+msgstr "Error: el valor que heu introduït no és vàlid."
#: env/lib/python3.11/site-packages/click/termui.py:176
#, python-brace-format
msgid "Error: {e.message}"
-msgstr ""
+msgstr "Error: {e.message}"
#: env/lib/python3.11/site-packages/click/termui.py:187
msgid "Error: The two entered values do not match."
-msgstr ""
+msgstr "Error: els dos valors introduïts no coincideixen."
#: env/lib/python3.11/site-packages/click/termui.py:243
msgid "Error: invalid input"
-msgstr ""
+msgstr "Error: entrada no vàlida"
#: env/lib/python3.11/site-packages/click/termui.py:773
msgid "Press any key to continue..."
-msgstr ""
+msgstr "Premeu qualsevol tecla per continuar..."
#: env/lib/python3.11/site-packages/click/types.py:266
#, python-brace-format
@@ -231,182 +235,193 @@ msgid ""
"Choose from:\n"
"\t{choices}"
msgstr ""
+"Trieu entre:\n"
+"\t{choices}"
#: env/lib/python3.11/site-packages/click/types.py:298
msgid "{value!r} is not {choice}."
msgid_plural "{value!r} is not one of {choices}."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "{value!r} no és {choice}."
+msgstr[1] "{value!r} no és un dels {choices}."
#: env/lib/python3.11/site-packages/click/types.py:392
msgid "{value!r} does not match the format {format}."
msgid_plural "{value!r} does not match the formats {formats}."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "{value!r} no coincideix amb el format {format}."
+msgstr[1] "{value!r} no coincideix amb els formats {formats}."
#: env/lib/python3.11/site-packages/click/types.py:414
msgid "{value!r} is not a valid {number_type}."
-msgstr ""
+msgstr "{value!r} no és un {number_type} vàlid."
#: env/lib/python3.11/site-packages/click/types.py:470
#, python-brace-format
msgid "{value} is not in the range {range}."
-msgstr ""
+msgstr "{value} no es troba a l'interval {range}."
#: env/lib/python3.11/site-packages/click/types.py:611
msgid "{value!r} is not a valid boolean."
-msgstr ""
+msgstr "{value!r} no és un booleà vàlid."
#: env/lib/python3.11/site-packages/click/types.py:635
msgid "{value!r} is not a valid UUID."
-msgstr ""
+msgstr "{value!r} no és un UUID vàlid."
#: env/lib/python3.11/site-packages/click/types.py:822
msgid "file"
-msgstr ""
+msgstr "arxiu"
#: env/lib/python3.11/site-packages/click/types.py:824
msgid "directory"
-msgstr ""
+msgstr "directori"
#: env/lib/python3.11/site-packages/click/types.py:826
msgid "path"
-msgstr ""
+msgstr "ruta"
#: env/lib/python3.11/site-packages/click/types.py:877
msgid "{name} {filename!r} does not exist."
-msgstr ""
+msgstr "{name} {filename!r} no existeix."
#: env/lib/python3.11/site-packages/click/types.py:886
msgid "{name} {filename!r} is a file."
-msgstr ""
+msgstr "{name} {filename!r} és un fitxer."
#: env/lib/python3.11/site-packages/click/types.py:894
#, python-brace-format
msgid "{name} '{filename}' is a directory."
-msgstr ""
+msgstr "{name} '{filename}' és un directori."
#: env/lib/python3.11/site-packages/click/types.py:903
msgid "{name} {filename!r} is not readable."
-msgstr ""
+msgstr "{name} {filename!r} no es pot llegir."
#: env/lib/python3.11/site-packages/click/types.py:912
msgid "{name} {filename!r} is not writable."
-msgstr ""
+msgstr "{name} {filename!r} no es pot escriure."
#: env/lib/python3.11/site-packages/click/types.py:921
msgid "{name} {filename!r} is not executable."
-msgstr ""
+msgstr "{name} {filename!r} no és executable."
#: env/lib/python3.11/site-packages/click/types.py:988
#, python-brace-format
msgid "{len_type} values are required, but {len_value} was given."
msgid_plural "{len_type} values are required, but {len_value} were given."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Es requereixen {len_type} valors, però s'ha donat {len_value}."
+msgstr[1] "Es requereixen {len_type} valors, però s'han donat {len_value}."
#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15
msgid "Messages"
-msgstr ""
+msgstr "Missatges"
#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8
msgid "Site Maps"
-msgstr ""
+msgstr "Mapes del lloc"
#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9
msgid "Static Files"
-msgstr ""
+msgstr "Fitxers estàtics"
#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7
msgid "Syndication"
-msgstr ""
+msgstr "Sindicació"
#. Translators: String used to replace omitted page numbers in elided page
#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10].
#: env/lib/python3.11/site-packages/django/core/paginator.py:30
msgid "…"
-msgstr ""
+msgstr "…"
#: env/lib/python3.11/site-packages/django/core/paginator.py:50
msgid "That page number is not an integer"
-msgstr ""
+msgstr "Aquest número de pàgina no és un nombre enter"
#: env/lib/python3.11/site-packages/django/core/paginator.py:52
msgid "That page number is less than 1"
-msgstr ""
+msgstr "Aquest número de pàgina és inferior a 1"
#: env/lib/python3.11/site-packages/django/core/paginator.py:54
msgid "That page contains no results"
-msgstr ""
+msgstr "Aquesta pàgina no conté cap resultat"
#: env/lib/python3.11/site-packages/django/core/validators.py:22
msgid "Enter a valid value."
-msgstr ""
+msgstr "Introduïu un valor vàlid."
#: env/lib/python3.11/site-packages/django/core/validators.py:104
#: env/lib/python3.11/site-packages/django/forms/fields.py:752
msgid "Enter a valid URL."
-msgstr ""
+msgstr "Introduïu un URL vàlid."
#: env/lib/python3.11/site-packages/django/core/validators.py:165
msgid "Enter a valid integer."
-msgstr ""
+msgstr "Introduïu un nombre enter vàlid."
#: env/lib/python3.11/site-packages/django/core/validators.py:176
msgid "Enter a valid email address."
-msgstr ""
+msgstr "Introduïu una adreça electrònica vàlida."
#. Translators: "letters" means latin letters: a-z and A-Z.
#: env/lib/python3.11/site-packages/django/core/validators.py:259
msgid ""
"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens."
msgstr ""
+"Introduïu un \"slug\" vàlid format per lletres, números, guions baixos o "
+"guions."
#: env/lib/python3.11/site-packages/django/core/validators.py:267
msgid ""
"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or "
"hyphens."
msgstr ""
+"Introduïu un \"slug\" vàlid format per lletres Unicode, números, guions "
+"baixos o guions."
#: env/lib/python3.11/site-packages/django/core/validators.py:279
#: env/lib/python3.11/site-packages/django/core/validators.py:287
#: env/lib/python3.11/site-packages/django/core/validators.py:316
msgid "Enter a valid IPv4 address."
-msgstr ""
+msgstr "Introduïu una adreça IPv4 vàlida."
#: env/lib/python3.11/site-packages/django/core/validators.py:296
#: env/lib/python3.11/site-packages/django/core/validators.py:317
msgid "Enter a valid IPv6 address."
-msgstr ""
+msgstr "Introduïu una adreça IPv6 vàlida."
#: env/lib/python3.11/site-packages/django/core/validators.py:308
#: env/lib/python3.11/site-packages/django/core/validators.py:315
msgid "Enter a valid IPv4 or IPv6 address."
-msgstr ""
+msgstr "Introduïu una adreça IPv4 o IPv6 vàlida."
#: env/lib/python3.11/site-packages/django/core/validators.py:351
msgid "Enter only digits separated by commas."
-msgstr ""
+msgstr "Introduïu només els dígits separats per comes."
#: env/lib/python3.11/site-packages/django/core/validators.py:357
#, python-format
msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)."
msgstr ""
+"Assegureu-vos que aquest valor sigui %(limit_value)s (és %(show_value)s)."
#: env/lib/python3.11/site-packages/django/core/validators.py:392
#, python-format
msgid "Ensure this value is less than or equal to %(limit_value)s."
msgstr ""
+"Assegureu-vos que aquest valor sigui inferior o igual a %(limit_value)s."
#: env/lib/python3.11/site-packages/django/core/validators.py:401
#, python-format
msgid "Ensure this value is greater than or equal to %(limit_value)s."
msgstr ""
+"Assegureu-vos que aquest valor sigui superior o igual a %(limit_value)s."
#: env/lib/python3.11/site-packages/django/core/validators.py:410
#, python-format
msgid "Ensure this value is a multiple of step size %(limit_value)s."
msgstr ""
+"Assegureu-vos que aquest valor sigui un múltiple de la mida del pas "
+"%(limit_value)s."
#: env/lib/python3.11/site-packages/django/core/validators.py:420
#, python-format
@@ -417,7 +432,11 @@ msgid_plural ""
"Ensure this value has at least %(limit_value)d characters (it has "
"%(show_value)d)."
msgstr[0] ""
+"Assegureu-vos que aquest valor tingui com a mínim %(limit_value)d caràcter "
+"(té %(show_value)d)."
msgstr[1] ""
+"Assegureu-vos que aquest valor tingui com a mínim %(limit_value)d caràcters "
+"(té %(show_value)d)."
#: env/lib/python3.11/site-packages/django/core/validators.py:438
#, python-format
@@ -428,27 +447,31 @@ msgid_plural ""
"Ensure this value has at most %(limit_value)d characters (it has "
"%(show_value)d)."
msgstr[0] ""
+"Assegureu-vos que aquest valor tingui com a màxim %(limit_value)d caràcters "
+"(té %(show_value)d)."
msgstr[1] ""
+"Assegureu-vos que aquest valor tingui com a màxim %(limit_value)d caràcters "
+"(té %(show_value)d)."
#: env/lib/python3.11/site-packages/django/core/validators.py:461
#: env/lib/python3.11/site-packages/django/forms/fields.py:347
#: env/lib/python3.11/site-packages/django/forms/fields.py:386
msgid "Enter a number."
-msgstr ""
+msgstr "Introduïu una xifra."
#: env/lib/python3.11/site-packages/django/core/validators.py:463
#, python-format
msgid "Ensure that there are no more than %(max)s digit in total."
msgid_plural "Ensure that there are no more than %(max)s digits in total."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Assegureu-vos que no hi hagi més de %(max)s dígits en total."
+msgstr[1] "Assegureu-vos que no hi hagi més de %(max)s dígits en total."
#: env/lib/python3.11/site-packages/django/core/validators.py:468
#, python-format
msgid "Ensure that there are no more than %(max)s decimal place."
msgid_plural "Ensure that there are no more than %(max)s decimal places."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Assegureu-vos que no hi hagi més de %(max)s decimals."
+msgstr[1] "Assegureu-vos que no hi hagi més de %(max)s decimals."
#: env/lib/python3.11/site-packages/django/core/validators.py:473
#, python-format
@@ -457,7 +480,9 @@ msgid ""
msgid_plural ""
"Ensure that there are no more than %(max)s digits before the decimal point."
msgstr[0] ""
+"Assegureu-vos que no hi hagi més de %(max)s dígits abans del punt decimal."
msgstr[1] ""
+"Assegureu-vos que no hi hagi més de %(max)s dígits abans del punt decimal."
#: env/lib/python3.11/site-packages/django/core/validators.py:544
#, python-format
@@ -465,43 +490,45 @@ msgid ""
"File extension “%(extension)s” is not allowed. Allowed extensions are: "
"%(allowed_extensions)s."
msgstr ""
+"L'extensió de fitxer “%(extension)s” no està permesa. Les extensions "
+"permeses són: %(allowed_extensions)s."
#: env/lib/python3.11/site-packages/django/core/validators.py:605
msgid "Null characters are not allowed."
-msgstr ""
+msgstr "No es permeten caràcters nuls."
#: env/lib/python3.11/site-packages/django/db/models/base.py:1423
#: env/lib/python3.11/site-packages/django/forms/models.py:893
msgid "and"
-msgstr ""
+msgstr "i"
#: env/lib/python3.11/site-packages/django/db/models/base.py:1425
#, python-format
msgid "%(model_name)s with this %(field_labels)s already exists."
-msgstr ""
+msgstr "%(model_name)s amb aquest %(field_labels)s ja existeix."
#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17
#, python-format
msgid "Constraint “%(name)s” is violated."
-msgstr ""
+msgstr "S'ha incomplert la restricció \"%(name)s\"."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128
#, python-format
msgid "Value %(value)r is not a valid choice."
-msgstr ""
+msgstr "El valor %(value)r no és una opció vàlida."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129
msgid "This field cannot be null."
-msgstr ""
+msgstr "Aquest camp no pot ser nul."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130
msgid "This field cannot be blank."
-msgstr ""
+msgstr "Aquest camp no pot estar en blanc."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
-msgstr ""
+msgstr "%(model_name)s amb aquest %(field_label)s ja existeix."
#. Translators: The 'lookup_type' is one of 'date', 'year' or
#. 'month'. Eg: "Title must be unique for pub_date year"
@@ -510,38 +537,39 @@ msgstr ""
msgid ""
"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s."
msgstr ""
+"%(field_label)s ha de ser únic per a %(date_field_label)s %(lookup_type)s."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173
#, python-format
msgid "Field of type: %(field_type)s"
-msgstr ""
+msgstr "Camp de tipus: %(field_type)s"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094
#, python-format
msgid "“%(value)s” value must be either True or False."
-msgstr ""
+msgstr "El valor \"%(value)s\" ha de ser Cert o Fals."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095
#, python-format
msgid "“%(value)s” value must be either True, False, or None."
-msgstr ""
+msgstr "El valor \"%(value)s\" ha de ser Cert, Fals o Cap."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097
msgid "Boolean (Either True or False)"
-msgstr ""
+msgstr "Booleà (cert o fals)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147
#, python-format
msgid "String (up to %(max_length)s)"
-msgstr ""
+msgstr "Cadena (fins a %(max_length)s)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149
msgid "String (unlimited)"
-msgstr ""
+msgstr "Cadena (il·limitada)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253
msgid "Comma-separated integers"
-msgstr ""
+msgstr "Nombres enters separats per comes"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354
#, python-format
@@ -549,6 +577,8 @@ msgid ""
"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD "
"format."
msgstr ""
+"El valor \"%(value)s\" té un format de data no vàlid. Ha de tenir el format "
+"AAAA-MM-DD."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493
@@ -557,10 +587,12 @@ msgid ""
"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid "
"date."
msgstr ""
+"El valor \"%(value)s\" té el format correcte (AAAA-MM-DD), però no és una "
+"data vàlida."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362
msgid "Date (without time)"
-msgstr ""
+msgstr "Data (sense hora)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489
#, python-format
@@ -568,6 +600,8 @@ msgid ""
"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[."
"uuuuuu]][TZ] format."
msgstr ""
+"El valor \"%(value)s\" té un format no vàlid. Ha de tenir el format AAAA-MM-"
+"DD HH:MM[:ss[.uuuuuu]][TZ]."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497
#, python-format
@@ -575,19 +609,21 @@ msgid ""
"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]"
"[TZ]) but it is an invalid date/time."
msgstr ""
+"El valor “%(value)s” té el format correcte (AAAA-MM-DD HH:MM[:ss[.uuuuuu]]"
+"[TZ]), però no és una data/hora vàlida."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502
msgid "Date (with time)"
-msgstr ""
+msgstr "Data (amb hora)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626
#, python-format
msgid "“%(value)s” value must be a decimal number."
-msgstr ""
+msgstr "El valor “%(value)s” ha de ser un nombre decimal."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628
msgid "Decimal number"
-msgstr ""
+msgstr "Nombre decimal"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789
#, python-format
@@ -595,83 +631,85 @@ msgid ""
"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[."
"uuuuuu] format."
msgstr ""
+"El valor \"%(value)s\" té un format no vàlid. Ha d'estar en format [DD] "
+"[[HH:]MM:]ss[.uuuuuu]."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793
msgid "Duration"
-msgstr ""
+msgstr "Durada"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845
msgid "Email address"
-msgstr ""
+msgstr "Adreça de correu electrònic"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870
msgid "File path"
-msgstr ""
+msgstr "Ruta al document"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948
#, python-format
msgid "“%(value)s” value must be a float."
-msgstr ""
+msgstr "El valor “%(value)s” ha de ser un valor flotant."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950
msgid "Floating point number"
-msgstr ""
+msgstr "Nombre de coma flotant"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990
#, python-format
msgid "“%(value)s” value must be an integer."
-msgstr ""
+msgstr "El valor “%(value)s” ha de ser un nombre enter."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992
msgid "Integer"
-msgstr ""
+msgstr "Enter"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088
msgid "Big (8 byte) integer"
-msgstr ""
+msgstr "Enter gran (8 bytes)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105
msgid "Small integer"
-msgstr ""
+msgstr "Enter petit"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113
msgid "IPv4 address"
-msgstr ""
+msgstr "Adreça IPv4"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144
msgid "IP address"
-msgstr ""
+msgstr "Adreça IP"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238
#, python-format
msgid "“%(value)s” value must be either None, True or False."
-msgstr ""
+msgstr "El valor \"%(value)s\" ha de ser Cap, Cert o Fals."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240
msgid "Boolean (Either True, False or None)"
-msgstr ""
+msgstr "Booleà (cert, fals o cap)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291
msgid "Positive big integer"
-msgstr ""
+msgstr "Enter gran positiu"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306
msgid "Positive integer"
-msgstr ""
+msgstr "Enter positiu"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321
msgid "Positive small integer"
-msgstr ""
+msgstr "Enter petit positiu"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337
#, python-format
msgid "Slug (up to %(max_length)s)"
-msgstr ""
+msgstr "Slug (fins a %(max_length)s)"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373
msgid "Text"
-msgstr ""
+msgstr "Text"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448
#, python-format
@@ -679,6 +717,8 @@ msgid ""
"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] "
"format."
msgstr ""
+"El valor \"%(value)s\" té un format no vàlid. Ha d'estar en format HH:MM[:"
+"ss[.uuuuuu]]."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452
#, python-format
@@ -686,120 +726,123 @@ msgid ""
"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an "
"invalid time."
msgstr ""
+"El valor \"%(value)s\" té el format correcte (HH:MM[:ss[.uuuuuu]]), però no "
+"és una hora vàlida."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456
msgid "Time"
-msgstr ""
+msgstr "Hora"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564
msgid "URL"
-msgstr ""
+msgstr "URL"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588
msgid "Raw binary data"
-msgstr ""
+msgstr "Dades binàries en brut"
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653
#, python-format
msgid "“%(value)s” is not a valid UUID."
-msgstr ""
+msgstr "“%(value)s” no és un UUID vàlid."
#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655
msgid "Universally unique identifier"
-msgstr ""
+msgstr "Identificador únic universal"
#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232
#: idhub/templates/idhub/admin/import.html:16
msgid "File"
-msgstr ""
+msgstr "Fitxer"
#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393
msgid "Image"
-msgstr ""
+msgstr "Imatge"
#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26
msgid "A JSON object"
-msgstr ""
+msgstr "Un objecte JSON"
#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28
msgid "Value must be valid JSON."
-msgstr ""
+msgstr "El valor ha de ser un JSON vàlid."
#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919
#, python-format
msgid "%(model)s instance with %(field)s %(value)r does not exist."
-msgstr ""
+msgstr "%(model)s instància amb %(field)s %(value)r no existeix."
#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921
msgid "Foreign Key (type determined by related field)"
-msgstr ""
+msgstr "Clau externa (tipus determinat pel camp relacionat)"
#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212
msgid "One-to-one relationship"
-msgstr ""
+msgstr "Relació un a un"
#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269
#, python-format
msgid "%(from)s-%(to)s relationship"
-msgstr ""
+msgstr "relació %(from)s-%(to)s"
#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271
#, python-format
msgid "%(from)s-%(to)s relationships"
-msgstr ""
+msgstr "relacions %(from)s-%(to)s"
#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319
msgid "Many-to-many relationship"
-msgstr ""
+msgstr "Relació de molts a molts"
#. Translators: If found as last label character, these punctuation
#. characters will prevent the default label_suffix to be appended to the label
#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184
msgid ":?.!"
-msgstr ""
+msgstr ":?.!"
#: env/lib/python3.11/site-packages/django/forms/fields.py:91
msgid "This field is required."
-msgstr ""
+msgstr "Aquest camp és obligatori."
#: env/lib/python3.11/site-packages/django/forms/fields.py:298
msgid "Enter a whole number."
-msgstr ""
+msgstr "Introduïu un número sencer."
#: env/lib/python3.11/site-packages/django/forms/fields.py:467
#: env/lib/python3.11/site-packages/django/forms/fields.py:1241
msgid "Enter a valid date."
-msgstr ""
+msgstr "Introduïu una data vàlida."
#: env/lib/python3.11/site-packages/django/forms/fields.py:490
#: env/lib/python3.11/site-packages/django/forms/fields.py:1242
msgid "Enter a valid time."
-msgstr ""
+msgstr "Introduïu una hora vàlida."
#: env/lib/python3.11/site-packages/django/forms/fields.py:517
msgid "Enter a valid date/time."
-msgstr ""
+msgstr "Introduïu una data/hora vàlida."
#: env/lib/python3.11/site-packages/django/forms/fields.py:551
msgid "Enter a valid duration."
-msgstr ""
+msgstr "Introduïu una durada vàlida."
#: env/lib/python3.11/site-packages/django/forms/fields.py:552
#, python-brace-format
msgid "The number of days must be between {min_days} and {max_days}."
-msgstr ""
+msgstr "El nombre de dies ha d'estar entre {min_days} i {max_days}."
#: env/lib/python3.11/site-packages/django/forms/fields.py:621
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
+"No s'ha enviat cap fitxer. Comproveu el tipus de codificació al formulari."
#: env/lib/python3.11/site-packages/django/forms/fields.py:622
msgid "No file was submitted."
-msgstr ""
+msgstr "No s'ha enviat cap fitxer."
#: env/lib/python3.11/site-packages/django/forms/fields.py:623
msgid "The submitted file is empty."
-msgstr ""
+msgstr "El fitxer enviat està buit."
#: env/lib/python3.11/site-packages/django/forms/fields.py:625
#, python-format
@@ -807,17 +850,23 @@ msgid "Ensure this filename has at most %(max)d character (it has %(length)d)."
msgid_plural ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)."
msgstr[0] ""
+"Assegureu-vos que aquest nom de fitxer tingui com a màxim %(max)d caràcter "
+"(té %(length)d)."
msgstr[1] ""
+"Assegureu-vos que aquest nom de fitxer tingui com a màxim %(max)d caràcters "
+"(té %(length)d)."
#: env/lib/python3.11/site-packages/django/forms/fields.py:630
msgid "Please either submit a file or check the clear checkbox, not both."
-msgstr ""
+msgstr "Envieu un fitxer o marqueu la casella de selecció clara, no tots dos."
#: env/lib/python3.11/site-packages/django/forms/fields.py:694
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
+"Carregueu una imatge vàlida. El fitxer que heu penjat no era una imatge o "
+"una imatge malmesa."
#: env/lib/python3.11/site-packages/django/forms/fields.py:857
#: env/lib/python3.11/site-packages/django/forms/fields.py:949
@@ -825,35 +874,36 @@ msgstr ""
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr ""
+"Seleccioneu una opció vàlida. %(value)s no és una de les opcions disponibles."
#: env/lib/python3.11/site-packages/django/forms/fields.py:951
#: env/lib/python3.11/site-packages/django/forms/fields.py:1070
#: env/lib/python3.11/site-packages/django/forms/models.py:1564
msgid "Enter a list of values."
-msgstr ""
+msgstr "Introduïu una llista de valors."
#: env/lib/python3.11/site-packages/django/forms/fields.py:1071
msgid "Enter a complete value."
-msgstr ""
+msgstr "Introduïu un valor complet."
#: env/lib/python3.11/site-packages/django/forms/fields.py:1310
msgid "Enter a valid UUID."
-msgstr ""
+msgstr "Introduïu un UUID vàlid."
#: env/lib/python3.11/site-packages/django/forms/fields.py:1340
msgid "Enter a valid JSON."
-msgstr ""
+msgstr "Introduïu un JSON vàlid."
#. Translators: This is the default suffix added to form field labels
#: env/lib/python3.11/site-packages/django/forms/forms.py:98
msgid ":"
-msgstr ""
+msgstr ":"
#: env/lib/python3.11/site-packages/django/forms/forms.py:244
#: env/lib/python3.11/site-packages/django/forms/forms.py:328
#, python-format
msgid "(Hidden field %(name)s) %(error)s"
-msgstr ""
+msgstr "(Camp ocult %(name)s) %(error)s"
#: env/lib/python3.11/site-packages/django/forms/formsets.py:63
#, python-format
@@ -861,25 +911,28 @@ msgid ""
"ManagementForm data is missing or has been tampered with. Missing fields: "
"%(field_names)s. You may need to file a bug report if the issue persists."
msgstr ""
+"Les dades del Fomulario de Gestión falten o s'han manipulat. Camps que "
+"falten: %(field_names)s. És possible que hàgiu de presentar un informe "
+"d'error si el problema persisteix."
#: env/lib/python3.11/site-packages/django/forms/formsets.py:67
#, python-format
msgid "Please submit at most %(num)d form."
msgid_plural "Please submit at most %(num)d forms."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Envieu com a màxim %(num)d formulari."
+msgstr[1] "Envieu com a màxim %(num)d formularis."
#: env/lib/python3.11/site-packages/django/forms/formsets.py:72
#, python-format
msgid "Please submit at least %(num)d form."
msgid_plural "Please submit at least %(num)d forms."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Envieu almenys %(num)d formulari."
+msgstr[1] "Envieu almenys %(num)d formularis."
#: env/lib/python3.11/site-packages/django/forms/formsets.py:484
#: env/lib/python3.11/site-packages/django/forms/formsets.py:491
msgid "Order"
-msgstr ""
+msgstr "Ordre"
#: env/lib/python3.11/site-packages/django/forms/formsets.py:499
#: idhub/templates/idhub/admin/dids.html:54
@@ -899,17 +952,17 @@ msgstr ""
#: idhub/templates/templates/musician/mailbox_check_delete.html:12
#: idhub/templates/templates/musician/mailbox_form.html:25
msgid "Delete"
-msgstr ""
+msgstr "Esborrar"
#: env/lib/python3.11/site-packages/django/forms/models.py:886
#, python-format
msgid "Please correct the duplicate data for %(field)s."
-msgstr ""
+msgstr "Corregiu les dades duplicades de %(field)s."
#: env/lib/python3.11/site-packages/django/forms/models.py:891
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
-msgstr ""
+msgstr "Corregiu les dades duplicades de %(field)s, que han de ser úniques."
#: env/lib/python3.11/site-packages/django/forms/models.py:898
#, python-format
@@ -917,23 +970,27 @@ msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s."
msgstr ""
+"Corregiu les dades duplicades per a %(field_name)s que han de ser úniques "
+"per a %(lookup)s a %(date_field)s."
#: env/lib/python3.11/site-packages/django/forms/models.py:907
msgid "Please correct the duplicate values below."
-msgstr ""
+msgstr "Corregiu els valors duplicats a continuació."
#: env/lib/python3.11/site-packages/django/forms/models.py:1338
msgid "The inline value did not match the parent instance."
-msgstr ""
+msgstr "El valor en línia no coincideix amb la instància principal."
#: env/lib/python3.11/site-packages/django/forms/models.py:1429
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
+"Seleccioneu una opció vàlida. Aquesta elecció no és una de les opcions "
+"disponibles."
#: env/lib/python3.11/site-packages/django/forms/models.py:1568
#, python-format
msgid "“%(pk)s” is not a valid value."
-msgstr ""
+msgstr "\"%(pk)s\" no és un valor vàlid."
#: env/lib/python3.11/site-packages/django/forms/utils.py:226
#, python-format
@@ -941,434 +998,436 @@ msgid ""
"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it "
"may be ambiguous or it may not exist."
msgstr ""
+"%(datetime)s no s'ha pogut interpretar a la zona horària "
+"%(current_timezone)s; pot ser ambigu o pot no existir."
#: env/lib/python3.11/site-packages/django/forms/widgets.py:463
msgid "Clear"
-msgstr ""
+msgstr "Neteja"
#: env/lib/python3.11/site-packages/django/forms/widgets.py:464
msgid "Currently"
-msgstr ""
+msgstr "Actualment"
#: env/lib/python3.11/site-packages/django/forms/widgets.py:465
msgid "Change"
-msgstr ""
+msgstr "Canviar"
#: env/lib/python3.11/site-packages/django/forms/widgets.py:794
msgid "Unknown"
-msgstr ""
+msgstr "Desconeguda"
#: env/lib/python3.11/site-packages/django/forms/widgets.py:795
msgid "Yes"
-msgstr ""
+msgstr "Yes"
#: env/lib/python3.11/site-packages/django/forms/widgets.py:796
msgid "No"
-msgstr ""
+msgstr "No"
#. Translators: Please do not add spaces around commas.
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:861
msgid "yes,no,maybe"
-msgstr ""
+msgstr "sí,no,potser"
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:891
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:908
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(size)d byte"
+msgstr[1] "%(size)d bytes"
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:910
#, python-format
msgid "%s KB"
-msgstr ""
+msgstr "%s KB"
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:912
#, python-format
msgid "%s MB"
-msgstr ""
+msgstr "%s MB"
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:914
#, python-format
msgid "%s GB"
-msgstr ""
+msgstr "%s GB"
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:916
#, python-format
msgid "%s TB"
-msgstr ""
+msgstr "%s TB"
#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:918
#, python-format
msgid "%s PB"
-msgstr ""
+msgstr "%s PB"
#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73
msgid "p.m."
-msgstr ""
+msgstr "p.m."
#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74
msgid "a.m."
-msgstr ""
+msgstr "a.m."
#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79
msgid "PM"
-msgstr ""
+msgstr "PM"
#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80
msgid "AM"
-msgstr ""
+msgstr "AM"
#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152
msgid "midnight"
-msgstr ""
+msgstr "mitjanit"
#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154
msgid "noon"
-msgstr ""
+msgstr "migdia"
#: env/lib/python3.11/site-packages/django/utils/dates.py:7
msgid "Monday"
-msgstr ""
+msgstr "Dilluns"
#: env/lib/python3.11/site-packages/django/utils/dates.py:8
msgid "Tuesday"
-msgstr ""
+msgstr "Dimarts"
#: env/lib/python3.11/site-packages/django/utils/dates.py:9
msgid "Wednesday"
-msgstr ""
+msgstr "Dimecres"
#: env/lib/python3.11/site-packages/django/utils/dates.py:10
msgid "Thursday"
-msgstr ""
+msgstr "Dijous"
#: env/lib/python3.11/site-packages/django/utils/dates.py:11
msgid "Friday"
-msgstr ""
+msgstr "Divendres"
#: env/lib/python3.11/site-packages/django/utils/dates.py:12
msgid "Saturday"
-msgstr ""
+msgstr "Dissabte"
#: env/lib/python3.11/site-packages/django/utils/dates.py:13
msgid "Sunday"
-msgstr ""
+msgstr "Diumenge"
#: env/lib/python3.11/site-packages/django/utils/dates.py:16
msgid "Mon"
-msgstr ""
+msgstr "Dl"
#: env/lib/python3.11/site-packages/django/utils/dates.py:17
msgid "Tue"
-msgstr ""
+msgstr "Dt"
#: env/lib/python3.11/site-packages/django/utils/dates.py:18
msgid "Wed"
-msgstr ""
+msgstr "Dx"
#: env/lib/python3.11/site-packages/django/utils/dates.py:19
msgid "Thu"
-msgstr ""
+msgstr "Dj"
#: env/lib/python3.11/site-packages/django/utils/dates.py:20
msgid "Fri"
-msgstr ""
+msgstr "Dv"
#: env/lib/python3.11/site-packages/django/utils/dates.py:21
msgid "Sat"
-msgstr ""
+msgstr "Ds"
#: env/lib/python3.11/site-packages/django/utils/dates.py:22
msgid "Sun"
-msgstr ""
+msgstr "Dg"
#: env/lib/python3.11/site-packages/django/utils/dates.py:25
msgid "January"
-msgstr ""
+msgstr "Gener"
#: env/lib/python3.11/site-packages/django/utils/dates.py:26
msgid "February"
-msgstr ""
+msgstr "Febrer"
#: env/lib/python3.11/site-packages/django/utils/dates.py:27
msgid "March"
-msgstr ""
+msgstr "Març"
#: env/lib/python3.11/site-packages/django/utils/dates.py:28
msgid "April"
-msgstr ""
+msgstr "Abril"
#: env/lib/python3.11/site-packages/django/utils/dates.py:29
msgid "May"
-msgstr ""
+msgstr "Maig"
#: env/lib/python3.11/site-packages/django/utils/dates.py:30
msgid "June"
-msgstr ""
+msgstr "Juny"
#: env/lib/python3.11/site-packages/django/utils/dates.py:31
msgid "July"
-msgstr ""
+msgstr "Juliol"
#: env/lib/python3.11/site-packages/django/utils/dates.py:32
msgid "August"
-msgstr ""
+msgstr "Agost"
#: env/lib/python3.11/site-packages/django/utils/dates.py:33
msgid "September"
-msgstr ""
+msgstr "Setembre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:34
msgid "October"
-msgstr ""
+msgstr "Octubre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:35
msgid "November"
-msgstr ""
+msgstr "Novembre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:36
msgid "December"
-msgstr ""
+msgstr "Desembre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:39
msgid "jan"
-msgstr ""
+msgstr "gen"
#: env/lib/python3.11/site-packages/django/utils/dates.py:40
msgid "feb"
-msgstr ""
+msgstr "feb"
#: env/lib/python3.11/site-packages/django/utils/dates.py:41
msgid "mar"
-msgstr ""
+msgstr "mar"
#: env/lib/python3.11/site-packages/django/utils/dates.py:42
msgid "apr"
-msgstr ""
+msgstr "apr"
#: env/lib/python3.11/site-packages/django/utils/dates.py:43
msgid "may"
-msgstr ""
+msgstr "maig"
#: env/lib/python3.11/site-packages/django/utils/dates.py:44
msgid "jun"
-msgstr ""
+msgstr "jun"
#: env/lib/python3.11/site-packages/django/utils/dates.py:45
msgid "jul"
-msgstr ""
+msgstr "jul"
#: env/lib/python3.11/site-packages/django/utils/dates.py:46
msgid "aug"
-msgstr ""
+msgstr "ago"
#: env/lib/python3.11/site-packages/django/utils/dates.py:47
msgid "sep"
-msgstr ""
+msgstr "set"
#: env/lib/python3.11/site-packages/django/utils/dates.py:48
msgid "oct"
-msgstr ""
+msgstr "oct"
#: env/lib/python3.11/site-packages/django/utils/dates.py:49
msgid "nov"
-msgstr ""
+msgstr "nov"
#: env/lib/python3.11/site-packages/django/utils/dates.py:50
msgid "dec"
-msgstr ""
+msgstr "des"
#: env/lib/python3.11/site-packages/django/utils/dates.py:53
msgctxt "abbrev. month"
msgid "Jan."
-msgstr ""
+msgstr "Gen."
#: env/lib/python3.11/site-packages/django/utils/dates.py:54
msgctxt "abbrev. month"
msgid "Feb."
-msgstr ""
+msgstr "Febr."
#: env/lib/python3.11/site-packages/django/utils/dates.py:55
msgctxt "abbrev. month"
msgid "March"
-msgstr ""
+msgstr "Març"
#: env/lib/python3.11/site-packages/django/utils/dates.py:56
msgctxt "abbrev. month"
msgid "April"
-msgstr ""
+msgstr "Abril"
#: env/lib/python3.11/site-packages/django/utils/dates.py:57
msgctxt "abbrev. month"
msgid "May"
-msgstr ""
+msgstr "Maig"
#: env/lib/python3.11/site-packages/django/utils/dates.py:58
msgctxt "abbrev. month"
msgid "June"
-msgstr ""
+msgstr "Juny"
#: env/lib/python3.11/site-packages/django/utils/dates.py:59
msgctxt "abbrev. month"
msgid "July"
-msgstr ""
+msgstr "Juliol"
#: env/lib/python3.11/site-packages/django/utils/dates.py:60
msgctxt "abbrev. month"
msgid "Aug."
-msgstr ""
+msgstr "Ago."
#: env/lib/python3.11/site-packages/django/utils/dates.py:61
msgctxt "abbrev. month"
msgid "Sept."
-msgstr ""
+msgstr "Sept."
#: env/lib/python3.11/site-packages/django/utils/dates.py:62
msgctxt "abbrev. month"
msgid "Oct."
-msgstr ""
+msgstr "Oct."
#: env/lib/python3.11/site-packages/django/utils/dates.py:63
msgctxt "abbrev. month"
msgid "Nov."
-msgstr ""
+msgstr "Nov."
#: env/lib/python3.11/site-packages/django/utils/dates.py:64
msgctxt "abbrev. month"
msgid "Dec."
-msgstr ""
+msgstr "Des."
#: env/lib/python3.11/site-packages/django/utils/dates.py:67
msgctxt "alt. month"
msgid "January"
-msgstr ""
+msgstr "Gener"
#: env/lib/python3.11/site-packages/django/utils/dates.py:68
msgctxt "alt. month"
msgid "February"
-msgstr ""
+msgstr "Febrer"
#: env/lib/python3.11/site-packages/django/utils/dates.py:69
msgctxt "alt. month"
msgid "March"
-msgstr ""
+msgstr "Març"
#: env/lib/python3.11/site-packages/django/utils/dates.py:70
msgctxt "alt. month"
msgid "April"
-msgstr ""
+msgstr "Abril"
#: env/lib/python3.11/site-packages/django/utils/dates.py:71
msgctxt "alt. month"
msgid "May"
-msgstr ""
+msgstr "Maig"
#: env/lib/python3.11/site-packages/django/utils/dates.py:72
msgctxt "alt. month"
msgid "June"
-msgstr ""
+msgstr "Juny"
#: env/lib/python3.11/site-packages/django/utils/dates.py:73
msgctxt "alt. month"
msgid "July"
-msgstr ""
+msgstr "Juliol"
#: env/lib/python3.11/site-packages/django/utils/dates.py:74
msgctxt "alt. month"
msgid "August"
-msgstr ""
+msgstr "Agost"
#: env/lib/python3.11/site-packages/django/utils/dates.py:75
msgctxt "alt. month"
msgid "September"
-msgstr ""
+msgstr "Setembre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:76
msgctxt "alt. month"
msgid "October"
-msgstr ""
+msgstr "Octubre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:77
msgctxt "alt. month"
msgid "November"
-msgstr ""
+msgstr "Novembre"
#: env/lib/python3.11/site-packages/django/utils/dates.py:78
msgctxt "alt. month"
msgid "December"
-msgstr ""
+msgstr "Desembre"
#: env/lib/python3.11/site-packages/django/utils/ipv6.py:8
msgid "This is not a valid IPv6 address."
-msgstr ""
+msgstr "Aquesta adreça IPv6 no és vàlida."
#: env/lib/python3.11/site-packages/django/utils/text.py:78
#, python-format
msgctxt "String to return when truncating text"
msgid "%(truncated_text)s…"
-msgstr ""
+msgstr "%(truncated_text)s…"
#: env/lib/python3.11/site-packages/django/utils/text.py:254
msgid "or"
-msgstr ""
+msgstr "o"
#. Translators: This string is used as a separator between list elements
#: env/lib/python3.11/site-packages/django/utils/text.py:273
#: env/lib/python3.11/site-packages/django/utils/timesince.py:135
msgid ", "
-msgstr ""
+msgstr ", "
#: env/lib/python3.11/site-packages/django/utils/timesince.py:8
#, python-format
msgid "%(num)d year"
msgid_plural "%(num)d years"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(num)d any"
+msgstr[1] "%(num)d anys"
#: env/lib/python3.11/site-packages/django/utils/timesince.py:9
#, python-format
msgid "%(num)d month"
msgid_plural "%(num)d months"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(num)d mes"
+msgstr[1] "%(num)d mesos"
#: env/lib/python3.11/site-packages/django/utils/timesince.py:10
#, python-format
msgid "%(num)d week"
msgid_plural "%(num)d weeks"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(num)d setmana"
+msgstr[1] "%(num)d setmanes"
#: env/lib/python3.11/site-packages/django/utils/timesince.py:11
#, python-format
msgid "%(num)d day"
msgid_plural "%(num)d days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(num)d dia"
+msgstr[1] "%(num)d dies"
#: env/lib/python3.11/site-packages/django/utils/timesince.py:12
#, python-format
msgid "%(num)d hour"
msgid_plural "%(num)d hours"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(num)d hora"
+msgstr[1] "%(num)d hores"
#: env/lib/python3.11/site-packages/django/utils/timesince.py:13
#, python-format
msgid "%(num)d minute"
msgid_plural "%(num)d minutes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(num)d minut"
+msgstr[1] "%(num)d minuts"
#: env/lib/python3.11/site-packages/django/views/csrf.py:111
msgid "Forbidden"
-msgstr ""
+msgstr "Prohibit"
#: env/lib/python3.11/site-packages/django/views/csrf.py:112
msgid "CSRF verification failed. Request aborted."
-msgstr ""
+msgstr "La verificació CSRF ha fallat. Sol·licitud avortada."
#: env/lib/python3.11/site-packages/django/views/csrf.py:116
msgid ""
@@ -1377,6 +1436,10 @@ msgid ""
"required for security reasons, to ensure that your browser is not being "
"hijacked by third parties."
msgstr ""
+"Esteu veient aquest missatge perquè aquest lloc HTTPS requereix que el "
+"vostre navegador web enviï una “Referer header”, però no se n'ha enviat cap. "
+"Aquesta capçalera és necessària per motius de seguretat, per garantir que el "
+"vostre navegador no sigui segrestat per tercers."
#: env/lib/python3.11/site-packages/django/views/csrf.py:122
msgid ""
@@ -1384,6 +1447,9 @@ msgid ""
"enable them, at least for this site, or for HTTPS connections, or for “same-"
"origin” requests."
msgstr ""
+"Si heu configurat el vostre navegador per desactivar les capçaleres "
+"\"Referer\", si us plau, torneu a habilitar-les, almenys per a aquest lloc, "
+"o per a connexions HTTPS, o per a sol·licituds del \"mateix origen\"."
#: env/lib/python3.11/site-packages/django/views/csrf.py:127
msgid ""
@@ -1393,6 +1459,11 @@ msgid ""
"If you’re concerned about privacy, use alternatives like for links to third-party sites."
msgstr ""
+"Si utilitzeu el etiqueteu o "
+"inclogueu la capçalera \"Política de referència: no-referrer\", traieu-les. "
+"La protecció CSRF requereix que la capçalera \"Referer\" faci una "
+"comprovació estricta de referència. Si us preocupa la privadesa, utilitzeu "
+"alternatives, com ara enllaços a llocs de tercers."
#: env/lib/python3.11/site-packages/django/views/csrf.py:136
msgid ""
@@ -1400,44 +1471,51 @@ msgid ""
"submitting forms. This cookie is required for security reasons, to ensure "
"that your browser is not being hijacked by third parties."
msgstr ""
+"Esteu veient aquest missatge perquè aquest lloc requereix una galeta CSRF a "
+"l'hora d'enviar formularis. Aquesta galeta és necessària per motius de "
+"seguretat, per garantir que el vostre navegador no sigui segrestat per "
+"tercers."
#: env/lib/python3.11/site-packages/django/views/csrf.py:142
msgid ""
"If you have configured your browser to disable cookies, please re-enable "
"them, at least for this site, or for “same-origin” requests."
msgstr ""
+"Si heu configurat el vostre navegador per desactivar les galetes, torneu-les "
+"a habilitar, almenys per a aquest lloc, o per a sol·licituds del \"mateix "
+"origen\"."
#: env/lib/python3.11/site-packages/django/views/csrf.py:148
msgid "More information is available with DEBUG=True."
-msgstr ""
+msgstr "Hi ha més informació disponible amb DEBUG=True."
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44
msgid "No year specified"
-msgstr ""
+msgstr "Cap any especificat"
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214
msgid "Date out of range"
-msgstr ""
+msgstr "Data fora d'interval"
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94
msgid "No month specified"
-msgstr ""
+msgstr "Sense mes especificat"
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147
msgid "No day specified"
-msgstr ""
+msgstr "Sense dia especificat"
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194
msgid "No week specified"
-msgstr ""
+msgstr "Cap setmana especificada"
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380
#, python-format
msgid "No %(verbose_name_plural)s available"
-msgstr ""
+msgstr "No hi ha %(verbose_name_plural)s disponible"
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652
#, python-format
@@ -1445,49 +1523,52 @@ msgid ""
"Future %(verbose_name_plural)s not available because %(class_name)s."
"allow_future is False."
msgstr ""
+"Futur %(verbose_name_plural)s no disponible perquè %(class_name)s."
+"allow_future és fals."
#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692
#, python-format
msgid "Invalid date string “%(datestr)s” given format “%(format)s”"
msgstr ""
+"La cadena de data \"%(datestr)s\" no és vàlida amb el format \"%(format)s\""
#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56
#, python-format
msgid "No %(verbose_name)s found matching the query"
-msgstr ""
+msgstr "No s'ha trobat cap %(verbose_name)s que coincideixi amb la consulta"
#: env/lib/python3.11/site-packages/django/views/generic/list.py:70
msgid "Page is not “last”, nor can it be converted to an int."
-msgstr ""
+msgstr "La pàgina no és \"última\", ni es pot convertir a int."
#: env/lib/python3.11/site-packages/django/views/generic/list.py:77
#, python-format
msgid "Invalid page (%(page_number)s): %(message)s"
-msgstr ""
+msgstr "Pàgina no vàlida (%(page_number)s): %(message)s"
#: env/lib/python3.11/site-packages/django/views/generic/list.py:169
#, python-format
msgid "Empty list and “%(class_name)s.allow_empty” is False."
-msgstr ""
+msgstr "Llista buida i \"%(class_name)s.allow_empty\" és Fals."
#: env/lib/python3.11/site-packages/django/views/static.py:38
msgid "Directory indexes are not allowed here."
-msgstr ""
+msgstr "Els índexs de directoris no es permeten aquí."
#: env/lib/python3.11/site-packages/django/views/static.py:40
#, python-format
msgid "“%(path)s” does not exist"
-msgstr ""
+msgstr "“%(path)s” no existeix"
#: env/lib/python3.11/site-packages/django/views/static.py:79
#, python-format
msgid "Index of %(directory)s"
-msgstr ""
+msgstr "Índex de %(directory)s"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220
msgid "The install worked successfully! Congratulations!"
-msgstr ""
+msgstr "La instal·lació ha funcionat correctament! Felicitats!"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206
#, python-format
@@ -1495,6 +1576,9 @@ msgid ""
"View release notes for Django %(version)s"
msgstr ""
+"Consulta les notes de la versió de Django "
+"%(version)s"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221
#, python-format
@@ -1504,275 +1588,279 @@ msgid ""
"rel=\"noopener\">DEBUG=True is in your settings file and you have not "
"configured any URLs."
msgstr ""
+"Esteu veient aquesta pàgina perquè DEBUG=True és al vostre fitxer de configuració i no heu "
+"configurat cap URL."
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229
msgid "Django Documentation"
-msgstr ""
+msgstr "Documentació de Django"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230
msgid "Topics, references, & how-to’s"
-msgstr ""
+msgstr "Temes, referències, & com fer-ho"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238
msgid "Tutorial: A Polling App"
-msgstr ""
+msgstr "Tutorial: una aplicació de votació"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239
msgid "Get started with Django"
-msgstr ""
+msgstr "Comença amb Django"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247
msgid "Django Community"
-msgstr ""
+msgstr "Comunitat Django"
#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248
msgid "Connect, get help, or contribute"
-msgstr ""
+msgstr "Connecteu-vos, obteniu ajuda o contribuïu"
#: env/lib/python3.11/site-packages/isort/main.py:158
msgid "show this help message and exit"
-msgstr ""
+msgstr "mostra aquest missatge d'ajuda i surt"
#: idhub/admin/forms.py:112
msgid "The user does not exist!"
-msgstr ""
+msgstr "L'usuari no existeix!"
#: idhub/admin/forms.py:154 idhub/admin/forms.py:168 idhub/admin/forms.py:177
msgid "This membership already exists!"
-msgstr ""
+msgstr "Aquesta subscripció ja existeix!"
#: idhub/admin/forms.py:159
msgid "The end date is less than the start date"
-msgstr ""
+msgstr "La data de finalització és inferior a la data d'inici"
#: idhub/admin/forms.py:206
msgid "Is not possible to have a duplicate role"
-msgstr ""
+msgstr "No és possible tenir una funció duplicada"
#: idhub/admin/views.py:48 idhub/templates/idhub/base.html:69
#: idhub/templates/idhub/base_admin.html:69 idhub/user/views.py:32
msgid "Dashboard"
-msgstr ""
+msgstr "Panell de control"
#: idhub/admin/views.py:49 idhub/user/views.py:33
msgid "Events"
-msgstr ""
+msgstr "Esdeveniments"
#: idhub/admin/views.py:61
msgid "User management"
-msgstr ""
+msgstr "Configuració de gestió d'usuaris"
#: idhub/admin/views.py:66
msgid "Access control management"
-msgstr ""
+msgstr "Gestió del control d'accés"
#: idhub/admin/views.py:71 idhub/user/views.py:74
msgid "Credential management"
-msgstr ""
+msgstr "Gestió de credencials"
#: idhub/admin/views.py:76
msgid "Template management"
-msgstr ""
+msgstr "Gestió de plantilles"
#: idhub/admin/views.py:81
msgid "Data file management"
-msgstr ""
+msgstr "Gestió de fitxers de dades"
#: idhub/admin/views.py:87 idhub/templates/idhub/base_admin.html:81
msgid "View users"
-msgstr ""
+msgstr "Veure usuaris"
#: idhub/admin/views.py:100
msgid "User personal information"
-msgstr ""
+msgstr "Informació personal de l'usuari"
#: idhub/admin/views.py:124
msgid "It is not possible deactivate your account!"
-msgstr ""
+msgstr "No és possible desactivar el teu compte!"
#: idhub/admin/views.py:148
msgid "It is not possible delete your account!"
-msgstr ""
+msgstr "No és possible esborrar el teu compte!"
#: idhub/admin/views.py:155
msgid "Update user"
-msgstr ""
+msgstr "Actualitza l'usuari"
#: idhub/admin/views.py:184
msgid "The account was updated successfully"
-msgstr ""
+msgstr "El compte s'ha actualitzat correctament"
#: idhub/admin/views.py:193 idhub/templates/idhub/base_admin.html:86
msgid "Add user"
-msgstr ""
+msgstr "Afegeix usuari"
#: idhub/admin/views.py:207
msgid "The account was created successfully"
-msgstr ""
+msgstr "El compte s'ha creat correctament"
#: idhub/admin/views.py:221 idhub/admin/views.py:265
msgid "Associate a membership to the user"
-msgstr ""
+msgstr "Associar una pertinença a l'usuari"
#: idhub/admin/views.py:252 idhub/admin/views.py:342
msgid "Membership created successfully"
-msgstr ""
+msgstr "Membre creat amb èxit"
#: idhub/admin/views.py:297 idhub/admin/views.py:377
msgid "Membership updated successfully"
-msgstr ""
+msgstr "Membre s'ha actualitzat correctament"
#: idhub/admin/views.py:312
msgid "Is not possible delete your account!"
-msgstr ""
+msgstr "No és possible eliminar el teu compte!"
#: idhub/admin/views.py:319
msgid "Add a user role to access a service"
-msgstr ""
+msgstr "Afegiu un rol d'usuari per accedir a un servei"
#: idhub/admin/views.py:355
msgid "Modify a user role to access a service"
-msgstr ""
+msgstr "Modificar un rol d'usuari per accedir a un servei"
#: idhub/admin/views.py:403 idhub/templates/idhub/base_admin.html:99
msgid "Manage roles"
-msgstr ""
+msgstr "Administrar rols"
#: idhub/admin/views.py:415
msgid "Add role"
-msgstr ""
+msgstr "Afegeix un rol"
#: idhub/admin/views.py:424
msgid "Role created successfully"
-msgstr ""
+msgstr "Rol creat correctament"
#: idhub/admin/views.py:431
msgid "Edit role"
-msgstr ""
+msgstr "Edita rol"
#: idhub/admin/views.py:446
msgid "Role updated successfully"
-msgstr ""
+msgstr "El rol s'ha actualitzat correctament"
#: idhub/admin/views.py:459
msgid "Role deleted successfully"
-msgstr ""
+msgstr "El rol s'ha suprimit correctament"
#: idhub/admin/views.py:466 idhub/templates/idhub/base_admin.html:104
msgid "Manage services"
-msgstr ""
+msgstr "Gestionar els serveis"
#: idhub/admin/views.py:478 idhub/templates/idhub/admin/services.html:35
msgid "Add service"
-msgstr ""
+msgstr "Afegeix un servei"
#: idhub/admin/views.py:492
msgid "Service created successfully"
-msgstr ""
+msgstr "Servei creat correctament"
#: idhub/admin/views.py:499
msgid "Modify service"
-msgstr ""
+msgstr "Modificar servei"
#: idhub/admin/views.py:519
msgid "Service updated successfully"
-msgstr ""
+msgstr "Servei actualitzat correctament"
#: idhub/admin/views.py:532
msgid "Service deleted successfully"
-msgstr ""
+msgstr "El servei s'ha suprimit correctament"
#: idhub/admin/views.py:539 idhub/templates/idhub/base_admin.html:117
msgid "View credentials"
-msgstr ""
+msgstr "Veure credencials"
#: idhub/admin/views.py:552
msgid "Change credential status"
-msgstr ""
+msgstr "Canvia l'estat de la credencial"
#: idhub/admin/views.py:594
msgid "Credential revoked successfully"
-msgstr ""
+msgstr "La credencial s'ha revocat correctament"
#: idhub/admin/views.py:616
msgid "Credential deleted successfully"
-msgstr ""
+msgstr "La credencial s'ha suprimit correctament"
#: idhub/admin/views.py:625
msgid "Manage identities (DID)"
-msgstr ""
+msgstr "Gestiona les identitats (DID)"
#: idhub/admin/views.py:638
msgid "Add a new organizational identity (DID)"
-msgstr ""
+msgstr "Afegeix una identitat organitzativa nova (DID)"
#: idhub/admin/views.py:650 idhub/user/views.py:194
msgid "DID created successfully"
-msgstr ""
+msgstr "DID creat correctament"
#: idhub/admin/views.py:657 idhub/admin/views.py:676
msgid "Organization Identities (DID)"
-msgstr ""
+msgstr "Identitats de l'organització (DID)"
#: idhub/admin/views.py:671 idhub/user/views.py:217
msgid "DID updated successfully"
-msgstr ""
+msgstr "DID s'ha actualitzat correctament"
#: idhub/admin/views.py:687 idhub/user/views.py:233
msgid "DID delete successfully"
-msgstr ""
+msgstr "DID s’ha suprimit correctament"
#: idhub/admin/views.py:693 idhub/templates/idhub/base_admin.html:132
msgid "View org. credentials"
-msgstr ""
+msgstr "Veure org. credencials"
#: idhub/admin/views.py:700 idhub/templates/idhub/base_admin.html:137
msgid "Configure credential issuance"
-msgstr ""
+msgstr "Configura l'emissió de credencials"
#: idhub/admin/views.py:707
msgid "View credential templates"
-msgstr ""
+msgstr "Veure plantilles de credencials"
#: idhub/admin/views.py:741
msgid "Upload template"
-msgstr ""
+msgstr "Carrega la plantilla"
#: idhub/admin/views.py:757
msgid "There are some errors in the file"
-msgstr ""
+msgstr "Hi ha alguns errors al fitxer"
#: idhub/admin/views.py:771
msgid "This template already exists!"
-msgstr ""
+msgstr "Aquesta plantilla ja existeix!"
#: idhub/admin/views.py:777 idhub/admin/views.py:822
msgid "This is not a valid schema!"
-msgstr ""
+msgstr "Aquest no és un esquema vàlid!"
#: idhub/admin/views.py:786
msgid "Import template"
-msgstr ""
+msgstr "Importa plantilla"
#: idhub/admin/views.py:814
msgid "The schema was added sucessfully"
-msgstr ""
+msgstr "L'esquema s'ha afegit correctament"
#: idhub/admin/views.py:839 idhub/templates/idhub/admin/import.html:31
msgid "Import data"
-msgstr ""
+msgstr "Importa les dades"
#: idhub/admin/views.py:852 idhub/admin/views.py:865
msgid "Import"
-msgstr ""
+msgstr "Importa"
#: idhub/admin/views.py:878
msgid "The file was imported successfully!"
-msgstr ""
+msgstr "El fitxer s'ha importat correctament!"
#: idhub/admin/views.py:883
msgid "Error importing the file!"
-msgstr ""
+msgstr "S'ha produït un error en importar el fitxer!"
#: idhub/models.py:67
#, python-brace-format
@@ -1780,60 +1868,69 @@ msgid ""
"The user {username} was registered: name: {first_name}, last name: "
"{last_name}"
msgstr ""
+"L'usuari {username} estava registrat: nom: {first_name}, cognom: {last_name}"
#: idhub/models.py:79
#, python-brace-format
msgid ""
"Welcome. You has been registered: name: {first_name}, last name: {last_name}"
-msgstr ""
+msgstr "Benvingut. Has estat registrat: nom: {first_name}, cognom: {last_name}"
#: idhub/models.py:92
#, python-brace-format
msgid ""
"The user '{username}' has request the update of the following information: "
msgstr ""
+"L'usuari '{username}' ha sol·licitat l'actualització de la informació "
+"següent: "
#: idhub/models.py:104
msgid "You have requested the update of the following information: "
-msgstr ""
+msgstr "Heu sol·licitat l'actualització de la informació següent: "
#: idhub/models.py:141
#, python-brace-format
msgid "The admin has deleted the user: username: {username}"
-msgstr ""
+msgstr "L'administrador ha suprimit l'usuari: nom d'usuari: {username}"
#: idhub/models.py:151
#, python-brace-format
msgid "New DID with DID-ID: '{did}' created by user '{username}'"
-msgstr ""
+msgstr "DID nou amb DID-ID: '{did}' creat per l'usuari '{username}'"
#: idhub/models.py:162
#, python-brace-format
msgid "New DID with label: '{label}' and DID-ID: '{did}' was created'"
-msgstr ""
+msgstr "S'ha creat un DID nou amb l'etiqueta: '{label}' i l'ID-DID: '{did}''"
#: idhub/models.py:174
#, python-brace-format
msgid ""
"The DID with label '{label}' and DID-ID: '{did}' was deleted from your wallet"
msgstr ""
+"El DID amb l'etiqueta \"{label}\" i el DID-ID: \"{did}\" s'ha suprimit de la "
+"vostra cartera"
#: idhub/models.py:186
#, python-brace-format
msgid "The credential of type '{type}' and ID: '{id}' was deleted"
-msgstr ""
+msgstr "S'ha suprimit la credencial del tipus \"{type}\" i ID: \"{id}\""
#: idhub/models.py:197
#, python-brace-format
msgid ""
"The credential of type '{type}' and ID: '{id}' was deleted from your wallet"
msgstr ""
+"La credencial del tipus \"{type}\" i ID: \"{id}\" s'han suprimit de la "
+"vostra cartera"
#: idhub/models.py:209
#, python-brace-format
msgid ""
"The credential of type '{type}' and ID: '{id}' was issued for user {username}"
msgstr ""
+"S'ha emès la credencial del tipus \"{type}\" i ID: \"{id}\" per a l'usuari "
+"{username}"
#: idhub/models.py:221
#, python-brace-format
@@ -1841,115 +1938,123 @@ msgid ""
"The credential of type '{type}' and ID: '{id}' was issued and stored in your "
"wallet"
msgstr ""
+"La credencial del tipus \"{type}\" i ID: \"{id}\" s'ha emès i s'ha "
+"emmagatzemat a la vostra cartera"
#: idhub/models.py:263
#, python-brace-format
msgid "The credential of type '{type}' was enabled for user {username}"
msgstr ""
+"La credencial del tipus \"{type}\" s'ha habilitat per a l'usuari {username}"
#: idhub/models.py:274
#, python-brace-format
msgid "You can request the '{type}' credential"
-msgstr ""
+msgstr "Podeu sol·licitar la credencial '{type}'"
#: idhub/models.py:285
#, python-brace-format
msgid "The credential of type '{type}' and ID: '{id}' was revoked for "
-msgstr ""
+msgstr "S'ha revocat la credencial del tipus \"{type}\" i ID: \"{id}\" per "
#: idhub/models.py:296
#, python-brace-format
msgid "The credential of type '{type}' and ID: '{id}' was revoked by admin"
msgstr ""
+"L'administrador ha revocat la credencial del tipus \"{type}\" i ID: \"{id}\""
#: idhub/models.py:308
msgid "A new role was created by admin"
-msgstr ""
+msgstr "Un nou rol va ser creat per l'administrador"
#: idhub/models.py:316
msgid "The role was modified by admin"
-msgstr ""
+msgstr "El rol va ser modificat per l'administrador"
#: idhub/models.py:324
msgid "The role was removed by admin"
-msgstr ""
+msgstr "L'administrador ha eliminat el rol"
#: idhub/models.py:332
msgid "A new service was created by admin"
-msgstr ""
+msgstr "Un nou servei ha estat creat per l'administrador"
#: idhub/models.py:340
msgid "The service was modified by admin"
-msgstr ""
+msgstr "El servei ha estat modificat per l'administrador"
#: idhub/models.py:348
msgid "The service was removed by admin"
-msgstr ""
+msgstr "El servei va ser eliminat per l'administrador"
#: idhub/models.py:356
#, python-brace-format
msgid ""
"New Organisational DID with label: '{label}' and DID-ID: '{did}' was created"
msgstr ""
+"S'ha creat un DID organitzatiu nou amb l'etiqueta: '{label}' i l'ID-DID: "
+"'{did}'"
#: idhub/models.py:367
#, python-brace-format
msgid ""
"Organisational DID with label: '{label}' and DID-ID: '{did}' was removed"
msgstr ""
+"S'ha eliminat el DID de l'organització amb l'etiqueta \"{label}\" i l'ID-"
+"DID: \"{did}\""
#: idhub/models.py:457
msgid "Enabled"
-msgstr ""
+msgstr "Habilitat"
#: idhub/models.py:458 idhub/templates/idhub/admin/credentials.html:17
#: idhub/templates/idhub/user/credentials.html:17
msgid "Issued"
-msgstr ""
+msgstr "Emès"
#: idhub/models.py:459
msgid "Revoked"
-msgstr ""
+msgstr "Revocat"
#: idhub/models.py:460
msgid "Expired"
-msgstr ""
+msgstr "Caducat"
#: idhub/models.py:576
msgid "Beneficiary"
-msgstr ""
+msgstr "Beneficiari"
#: idhub/models.py:577
msgid "Employee"
-msgstr ""
+msgstr "Empleat"
#: idhub/models.py:578
msgid "Member"
-msgstr ""
+msgstr "Membre"
#: idhub/models.py:580
msgid "Type of membership"
-msgstr ""
+msgstr "Tipus de pertinença"
#: idhub/models.py:582
msgid "Start date"
-msgstr ""
+msgstr "Data d'inici"
#: idhub/models.py:583
msgid "What date did the membership start?"
-msgstr ""
+msgstr "Quina data va començar la subscripció?"
#: idhub/models.py:588
msgid "End date"
-msgstr ""
+msgstr "Data final"
#: idhub/models.py:589
msgid "What date will the membership end?"
-msgstr ""
+msgstr "Quina data finalitzarà la subscripció?"
#: idhub/models.py:605
msgid "name"
-msgstr ""
+msgstr "nom"
#: idhub/models.py:606 idhub/models.py:614
#: idhub/templates/idhub/admin/dashboard.html:14
@@ -1961,31 +2066,31 @@ msgstr ""
#: idhub/templates/idhub/user/dashboard.html:14
#: idhub/templates/idhub/user/roles.html:16
msgid "Description"
-msgstr ""
+msgstr "Descripción"
#: idhub/models.py:613 idhub/templates/templates/musician/addresses.html:16
msgid "Domain"
-msgstr ""
+msgstr "Domini"
#: idhub/models.py:622
msgid "None"
-msgstr ""
+msgstr "Sense"
#: idhub/models.py:647
msgid "Url where to send the presentation"
-msgstr ""
+msgstr "URL on enviar la presentació"
#: idhub/templates/auth/login.html:47
msgid "Log in"
-msgstr ""
+msgstr "Entra"
#: idhub/templates/auth/login.html:52
msgid "Forgot your password? Click here to recover"
-msgstr ""
+msgstr "Has oblidat la teva contrasenya? Feu clic aquí per recuperar-la"
#: idhub/templates/auth/login_base.html:91
msgid "Forgot your password?"
-msgstr ""
+msgstr "Heu oblidat la contrasenya?"
#: idhub/templates/auth/login_base.html:97
#, python-format
@@ -1993,76 +2098,91 @@ msgid ""
"Send an email to %(support_email)s "
"including your username and we will provide instructions."
msgstr ""
+"Envieu un correu electrònic a %(support_email)s amb el vostre nom d'usuari i us "
+"proporcionarem instruccions."
#: idhub/templates/auth/password_reset.html:8
msgid "Password reset"
-msgstr ""
+msgstr "Restableix contrasenya"
#: idhub/templates/auth/password_reset.html:9
msgid ""
"Forgotten your password? Enter your email address below, and we'll email "
"instructions for setting a new one."
msgstr ""
+"Has oblidat la teva contrassenya? Introduïu la vostra adreça de correu "
+"electrònic a continuació i us enviarem les instruccions per configurar-ne "
+"una de nova."
#: idhub/templates/auth/password_reset.html:21
msgid "Reset my password"
-msgstr ""
+msgstr "Reinicia la meva contrasenya"
#: idhub/templates/auth/password_reset_complete.html:9
msgid "Password reset complete"
-msgstr ""
+msgstr "S'ha completat la reinicialització de la contrasenya"
#: idhub/templates/auth/password_reset_complete.html:10
msgid "Your password has been set. You may go ahead and log in now."
-msgstr ""
+msgstr "S'ha establert la vostra contrasenya. Ara podeu entrar."
#: idhub/templates/auth/password_reset_complete.html:11 idhub/views.py:9
msgid "Login"
-msgstr ""
+msgstr "Inicia la sessió"
#: idhub/templates/auth/password_reset_confirm.html:9
msgid "Enter new password"
-msgstr ""
+msgstr "Introduïu la contrasenya nova"
#: idhub/templates/auth/password_reset_confirm.html:10
msgid ""
"Please enter your new password twice so we can verify you typed it in "
"correctly."
msgstr ""
+"Introduïu la contrasenya nova dues vegades, de manera que ens assegurem que "
+"l'heu escrita correctament."
#: idhub/templates/auth/password_reset_confirm.html:21
msgid "Change my password"
-msgstr ""
+msgstr "Canvia la contrasenya"
#: idhub/templates/auth/password_reset_confirm.html:29
msgid "Password reset unsuccessful"
-msgstr ""
+msgstr "No s'ha pogut recuperar la contrasenya"
#: idhub/templates/auth/password_reset_confirm.html:30
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used."
msgstr ""
+"L'enllaç de restabliment de la contrasenya no era vàlid, possiblement perquè "
+"ja s'ha utilitzat."
#: idhub/templates/auth/password_reset_confirm.html:31
msgid "Please request a new password reset."
-msgstr ""
+msgstr "Sol·liciteu un restabliment de la contrasenya nova."
#: idhub/templates/auth/password_reset_done.html:7
msgid "Password reset sent"
-msgstr ""
+msgstr "S'ha enviat el restabliment de la contrasenya"
#: idhub/templates/auth/password_reset_done.html:9
msgid ""
"We've emailed you instructions for setting your password, if an account "
"exists with the email you entered. You should receive them shortly."
msgstr ""
+"Us hem enviat per correu electrònic instruccions per configurar la vostra "
+"contrasenya, si hi ha un compte amb el correu electrònic que heu introduït. "
+"Les hauríeu de rebre en breu."
#: idhub/templates/auth/password_reset_done.html:11
msgid ""
"If you don't receive an email, please make sure you've entered the address "
"you registered with, and check your spam folder."
msgstr ""
+"Si no rebeu cap correu electrònic, assegureu-vos que heu introduït l'adreça "
+"amb la qual us heu registrat i comproveu la vostra carpeta de correu brossa."
#: idhub/templates/auth/password_reset_email.html:3
#: idhub/templates/auth/password_reset_email.txt:2
@@ -2071,34 +2191,36 @@ msgid ""
"You're receiving this email because you requested a password reset for your "
"user account at %(site_name)s."
msgstr ""
+"Heu rebut aquest correu electrònic perquè heu sol·licitat un restabliment de "
+"la contrasenya per al vostre compte d'usuari a %(site_name)s."
#: idhub/templates/auth/password_reset_email.html:7
#: idhub/templates/auth/password_reset_email.txt:4
msgid "Please go to the following page and choose a new password:"
-msgstr ""
+msgstr "Aneu a la pàgina següent i trieu una nova contrasenya:"
#: idhub/templates/auth/password_reset_email.html:19
#: idhub/templates/auth/password_reset_email.txt:8
msgid "Your username, in case you've forgotten:"
-msgstr ""
+msgstr "El vostre nom d'usuari, en cas que l'heu oblidat:"
#: idhub/templates/auth/password_reset_email.html:23
#: idhub/templates/auth/password_reset_email.txt:10
#: idhub/templates/idhub/admin/registration/activate_user_email.html:24
#: idhub/templates/idhub/admin/registration/activate_user_email.txt:15
msgid "Thanks for using our site!"
-msgstr ""
+msgstr "Gràcies per utilitzar el nostre lloc!"
#: idhub/templates/auth/password_reset_email.html:27
#: idhub/templates/auth/password_reset_email.txt:12
#, python-format
msgid "The %(site_name)s team"
-msgstr ""
+msgstr "L'equip de %(site_name)s"
#: idhub/templates/auth/password_reset_subject.txt:2
#, python-format
msgid "Password reset on %(site_name)s"
-msgstr ""
+msgstr "Restabliment de la contrasenya a %(site_name)s"
#: idhub/templates/idhub/admin/credentials.html:15
#: idhub/templates/idhub/user/credentials.html:15
@@ -2107,38 +2229,38 @@ msgstr ""
#: idhub/templates/templates/musician/databases.html:17
#: idhub/templates/templates/musician/domain_detail.html:17
msgid "Type"
-msgstr ""
+msgstr "Tipus"
#: idhub/templates/idhub/admin/credentials.html:16
#: idhub/templates/idhub/user/credentials.html:16
msgid "Details"
-msgstr ""
+msgstr "Detalls"
#: idhub/templates/idhub/admin/credentials.html:18
#: idhub/templates/idhub/user/credentials.html:18
msgid "Status"
-msgstr ""
+msgstr "Status"
#: idhub/templates/idhub/admin/credentials.html:19
msgid "User"
-msgstr ""
+msgstr "Usuaris"
#: idhub/templates/idhub/admin/credentials.html:31
#: idhub/templates/idhub/admin/people.html:33
#: idhub/templates/idhub/admin/schemas.html:30
msgid "View"
-msgstr ""
+msgstr "Vista"
#: idhub/templates/idhub/admin/dashboard.html:13
msgid "Event"
-msgstr ""
+msgstr "Esdeveniment"
#: idhub/templates/idhub/admin/dashboard.html:15
#: idhub/templates/idhub/admin/dids.html:15
#: idhub/templates/idhub/user/dashboard.html:15
#: idhub/templates/idhub/user/dids.html:15
msgid "Date"
-msgstr ""
+msgstr "Data"
#: idhub/templates/idhub/admin/did_register.html:29
#: idhub/templates/idhub/admin/import_add.html:27
@@ -2158,7 +2280,7 @@ msgstr ""
#: idhub/templates/templates/musician/mailbox_check_delete.html:13
#: idhub/templates/templates/musician/mailbox_form.html:20
msgid "Cancel"
-msgstr ""
+msgstr "Cancel·lar"
#: idhub/templates/idhub/admin/did_register.html:30
#: idhub/templates/idhub/admin/import_add.html:28
@@ -2174,12 +2296,12 @@ msgstr ""
#: idhub/templates/templates/musician/mailbox_change_password.html:12
#: idhub/templates/templates/musician/mailbox_form.html:21
msgid "Save"
-msgstr ""
+msgstr "Desar"
#: idhub/templates/idhub/admin/dids.html:16
#: idhub/templates/idhub/user/dids.html:16
msgid "Label"
-msgstr ""
+msgstr "Etiqueta"
#: idhub/templates/idhub/admin/dids.html:28
#: idhub/templates/idhub/admin/roles.html:26
@@ -2188,77 +2310,77 @@ msgstr ""
#: idhub/templates/idhub/admin/user_edit.html:86
#: idhub/templates/idhub/user/dids.html:28
msgid "Edit"
-msgstr ""
+msgstr "Editar"
#: idhub/templates/idhub/admin/dids.html:29
#: idhub/templates/idhub/admin/schemas.html:31
#: idhub/templates/idhub/user/dids.html:29
msgid "Remove"
-msgstr ""
+msgstr "Esborra"
#: idhub/templates/idhub/admin/dids.html:35
msgid "Add identity"
-msgstr ""
+msgstr "Afegeix identitat"
#: idhub/templates/idhub/admin/dids.html:46
#: idhub/templates/idhub/user/dids.html:46
msgid "Delete DID"
-msgstr ""
+msgstr "Suprimeix DID"
#: idhub/templates/idhub/admin/dids.html:50
#: idhub/templates/idhub/user/dids.html:50
msgid "Are you sure that you want delete this DID?"
-msgstr ""
+msgstr "Esteu segur que voleu suprimir aquest DID?"
#: idhub/templates/idhub/admin/import.html:15
#: idhub/templates/idhub/admin/schemas.html:15
msgid "Created"
-msgstr ""
+msgstr "Creat"
#: idhub/templates/idhub/admin/import.html:17
msgid "Success"
-msgstr ""
+msgstr "Ha tingut èxit"
#: idhub/templates/idhub/admin/issue_credentials.html:14
#: idhub/templates/idhub/admin/issue_credentials.html:72
msgid "Revoke"
-msgstr ""
+msgstr "Revoca"
#: idhub/templates/idhub/admin/issue_credentials.html:54
#: idhub/templates/idhub/user/credential.html:41
msgid "View in JSON format"
-msgstr ""
+msgstr "Visualitza en format JSON"
#: idhub/templates/idhub/admin/issue_credentials.html:64
msgid "Revoke credential"
-msgstr ""
+msgstr "Revoca la credencial"
#: idhub/templates/idhub/admin/issue_credentials.html:68
msgid "Are you sure that you want revoke this credential?"
-msgstr ""
+msgstr "Esteu segur que voleu revocar aquesta credencial?"
#: idhub/templates/idhub/admin/issue_credentials.html:82
msgid "Delete credential"
-msgstr ""
+msgstr "Suprimeix la credencial"
#: idhub/templates/idhub/admin/issue_credentials.html:86
msgid "Are you sure that you want delete this Credential?"
-msgstr ""
+msgstr "Esteu segur que voleu suprimir aquesta credencial?"
#: idhub/templates/idhub/admin/people.html:13
msgid "Last name"
-msgstr ""
+msgstr "Cognoms"
#: idhub/templates/idhub/admin/people.html:14
msgid "First name"
-msgstr ""
+msgstr "Nom"
#: idhub/templates/idhub/admin/people.html:16
#: idhub/templates/idhub/admin/user.html:62
#: idhub/templates/idhub/admin/user_edit.html:41
#: idhub/templates/idhub/user/profile.html:43
msgid "Membership"
-msgstr ""
+msgstr "Afiliació"
#: idhub/templates/idhub/admin/people.html:17
#: idhub/templates/idhub/admin/roles.html:15
@@ -2267,12 +2389,12 @@ msgstr ""
#: idhub/templates/idhub/admin/user_edit.html:73
#: idhub/templates/idhub/user/roles.html:15
msgid "Role"
-msgstr ""
+msgstr "Càrrec"
#: idhub/templates/idhub/admin/registration/activate_user_email.html:2
#: idhub/templates/idhub/admin/registration/activate_user_subject.txt:2
msgid "IdHub"
-msgstr ""
+msgstr "IdHub"
#: idhub/templates/idhub/admin/registration/activate_user_email.html:4
#: idhub/templates/idhub/admin/registration/activate_user_email.txt:5
@@ -2281,529 +2403,536 @@ msgid ""
"You're receiving this email because your user account at %(site)s has been "
"activated."
msgstr ""
+"Heu rebut aquest correu electrònic perquè el vostre compte d'usuari a "
+"%(site)s s'ha activat."
#: idhub/templates/idhub/admin/registration/activate_user_email.html:8
#: idhub/templates/idhub/admin/registration/activate_user_email.txt:7
msgid "Your username is:"
-msgstr ""
+msgstr "El teu nom d'usuari és:"
#: idhub/templates/idhub/admin/registration/activate_user_email.html:12
#: idhub/templates/idhub/admin/registration/activate_user_email.txt:9
msgid "Please go to the following page and choose a password:"
-msgstr ""
+msgstr "Aneu a la pàgina següent i trieu una contrasenya:"
#: idhub/templates/idhub/admin/registration/activate_user_email.html:28
#: idhub/templates/idhub/admin/registration/activate_user_email.txt:17
#, python-format
msgid "The %(site)s team"
-msgstr ""
+msgstr "L'equip de %(site)s"
#: idhub/templates/idhub/admin/registration/activate_user_email.txt:3
msgid "Idhub"
-msgstr ""
+msgstr "Idhub"
#: idhub/templates/idhub/admin/registration/activate_user_subject.txt:3
#, python-format
msgid "User activation on %(site)s"
-msgstr ""
+msgstr "Activació d'usuari a %(site)s"
#: idhub/templates/idhub/admin/roles.html:33
#: idhub/templates/idhub/admin/user_edit.html:93
msgid "Add Role"
-msgstr ""
+msgstr "Afegeix un rol"
#: idhub/templates/idhub/admin/schemas.html:16
msgid "Template file"
-msgstr ""
+msgstr "Fitxer de plantilla"
#: idhub/templates/idhub/admin/schemas.html:17
#: idhub/templates/templates/musician/mailboxes.html:14
msgid "Name"
-msgstr ""
+msgstr "Name"
#: idhub/templates/idhub/admin/schemas.html:37
#: idhub/templates/idhub/admin/schemas_import.html:29
msgid "Add template"
-msgstr ""
+msgstr "Afegeix plantilla"
#: idhub/templates/idhub/admin/schemas.html:48
msgid "Delete template"
-msgstr ""
+msgstr "Suprimeix la plantilla"
#: idhub/templates/idhub/admin/schemas.html:52
msgid "Are you sure that you want delete this template?"
-msgstr ""
+msgstr "Segur que voleu suprimir aquesta plantilla?"
#: idhub/templates/idhub/admin/schemas_import.html:15
msgid "Available templates"
-msgstr ""
+msgstr "Plantilles disponibles"
#: idhub/templates/idhub/admin/schemas_import.html:23
msgid "Add"
-msgstr ""
+msgstr "Afegeix"
#: idhub/templates/idhub/admin/services.html:15
#: idhub/templates/idhub/admin/user.html:89
#: idhub/templates/idhub/admin/user_edit.html:75
#: idhub/templates/idhub/user/roles.html:17
msgid "Service"
-msgstr ""
+msgstr "Servei"
#: idhub/templates/idhub/admin/user.html:13
msgid "Modify"
-msgstr ""
+msgstr "Modifica"
#: idhub/templates/idhub/admin/user.html:14
msgid "Deactivate"
-msgstr ""
+msgstr "Desactiva"
#: idhub/templates/idhub/admin/user.html:14
msgid "Activate"
-msgstr ""
+msgstr "Activa"
#: idhub/templates/idhub/admin/user.html:63
#: idhub/templates/idhub/admin/user_edit.html:42
#: idhub/templates/idhub/user/profile.html:44
msgid "From"
-msgstr ""
+msgstr "Remitent"
#: idhub/templates/idhub/admin/user.html:64
#: idhub/templates/idhub/admin/user_edit.html:43
#: idhub/templates/idhub/user/profile.html:45
msgid "To"
-msgstr ""
+msgstr "Destinatari"
#: idhub/templates/idhub/admin/user.html:112
msgid "Delete user"
-msgstr ""
+msgstr "Suprimeix l'usuari"
#: idhub/templates/idhub/admin/user.html:116
msgid "Are you sure that you want delete this user?"
-msgstr ""
+msgstr "Esteu segur que voleu suprimir aquest usuari?"
#: idhub/templates/idhub/admin/user_edit.html:61
msgid "Add membership"
-msgstr ""
+msgstr "Afegeix membres"
#: idhub/templates/idhub/base.html:76
msgid "My information"
-msgstr ""
+msgstr "La meva informació"
#: idhub/templates/idhub/base.html:81
msgid "My personal information"
-msgstr ""
+msgstr "La meva informació personal"
#: idhub/templates/idhub/base.html:86 idhub/user/views.py:62
msgid "My roles"
-msgstr ""
+msgstr "Els meus rols"
#: idhub/templates/idhub/base.html:91 idhub/user/views.py:68
msgid "GDPR info"
-msgstr ""
+msgstr "Informació GDPR"
#: idhub/templates/idhub/base.html:99 idhub/user/views.py:26
msgid "My wallet"
-msgstr ""
+msgstr "La meva cartera"
#: idhub/templates/idhub/base.html:104 idhub/user/views.py:169
#: idhub/user/views.py:203 idhub/user/views.py:222
msgid "Identities (DIDs)"
-msgstr ""
+msgstr "Identitats (DID)"
#: idhub/templates/idhub/base.html:109
msgid "My credentials"
-msgstr ""
+msgstr "Les meves credencials"
#: idhub/templates/idhub/base.html:114
msgid "Request a credential"
-msgstr ""
+msgstr "Sol·licitar una credencial"
#: idhub/templates/idhub/base.html:119
msgid "Present a credential"
-msgstr ""
+msgstr "Presentar una credencial"
#: idhub/templates/idhub/base_admin.html:76
msgid "Users"
-msgstr ""
+msgstr "Usuaris"
#: idhub/templates/idhub/base_admin.html:94
msgid "Roles"
-msgstr ""
+msgstr "Rols"
#: idhub/templates/idhub/base_admin.html:112
#: idhub/templates/idhub/user/profile.html:46
msgid "Credentials"
-msgstr ""
+msgstr "Credencials"
#: idhub/templates/idhub/base_admin.html:122
msgid "Organization's wallet"
-msgstr ""
+msgstr "Cartera de l'organització"
#: idhub/templates/idhub/base_admin.html:127
msgid "Manage Identities (DIDs)"
-msgstr ""
+msgstr "Gestionar identitats (DID)"
#: idhub/templates/idhub/base_admin.html:147
msgid "Templates"
-msgstr ""
+msgstr "Plantilles"
#: idhub/templates/idhub/base_admin.html:153
msgid "Data"
-msgstr ""
+msgstr "Dades"
#: idhub/templates/idhub/user/credentials_presentation.html:30
msgid "Send"
-msgstr ""
+msgstr "Enviar"
#: idhub/templates/idhub/user/credentials_request.html:30
msgid "Request"
-msgstr ""
+msgstr "Sol·licitud"
#: idhub/templates/idhub/user/dids.html:35
msgid "Add Identity"
-msgstr ""
+msgstr "Afegeix identitat"
#: idhub/templates/idhub/user/profile.html:13
msgid "ARCO Forms"
-msgstr ""
+msgstr "Formularis ARCO"
#: idhub/templates/idhub/user/profile.html:14
msgid "Notice of Privacy"
-msgstr ""
+msgstr "Avís de Privacitat"
#: idhub/templates/templates/musician/address_check_delete.html:7
#, python-format
msgid "Are you sure that you want remove the address: \"%(address_name)s\"?"
-msgstr ""
+msgstr "Esteu segur que voleu eliminar l'adreça: \"%(address_name)s\"?"
#: idhub/templates/templates/musician/address_check_delete.html:8
#: idhub/templates/templates/musician/mailbox_check_delete.html:11
msgid "WARNING: This action cannot be undone."
-msgstr ""
+msgstr "ADVERTIMENT: Aquesta acció no es pot desfer."
#: idhub/templates/templates/musician/addresses.html:15
msgid "Email"
-msgstr ""
+msgstr "Adreça electrònica"
#: idhub/templates/templates/musician/addresses.html:17
#: idhub/templates/templates/musician/mail_base.html:22
msgid "Mailboxes"
-msgstr ""
+msgstr "Bústies"
#: idhub/templates/templates/musician/addresses.html:18
msgid "Forward"
-msgstr ""
+msgstr "Reenvia"
#: idhub/templates/templates/musician/addresses.html:38
msgid "New mail address"
-msgstr ""
+msgstr "Nova adreça de correu"
#: idhub/templates/templates/musician/billing.html:6
msgid "Billing"
-msgstr ""
+msgstr "Facturació"
#: idhub/templates/templates/musician/billing.html:7
msgid "Billing page description."
-msgstr ""
+msgstr "Descripció de la pàgina de facturació."
#: idhub/templates/templates/musician/billing.html:19
msgid "Number"
-msgstr ""
+msgstr "Número"
#: idhub/templates/templates/musician/billing.html:20
msgid "Bill date"
-msgstr ""
+msgstr "Data de facturació"
#: idhub/templates/templates/musician/billing.html:22
msgid "Total"
-msgstr ""
+msgstr "Total"
#: idhub/templates/templates/musician/billing.html:23
msgid "Download PDF"
-msgstr ""
+msgstr "Descarregar PDF"
#: idhub/templates/templates/musician/components/table_paginator.html:15
msgid "Previous"
-msgstr ""
+msgstr "Anterior"
#: idhub/templates/templates/musician/components/table_paginator.html:29
msgid "Next"
-msgstr ""
+msgstr "Següent"
#: idhub/templates/templates/musician/dashboard.html:6
msgid "Welcome back"
-msgstr ""
+msgstr "Benvingut de nou"
#: idhub/templates/templates/musician/dashboard.html:8
#, python-format
msgid "Last time you logged in was: %(last_login)s"
-msgstr ""
+msgstr "La darrera vegada que vas iniciar sessió va ser: %(last_login)s"
#: idhub/templates/templates/musician/dashboard.html:10
msgid "It's the first time you log into the system, welcome on board!"
-msgstr ""
+msgstr "És la primera vegada que inicieu sessió al sistema, benvingut a bord!"
#: idhub/templates/templates/musician/dashboard.html:29
msgid "Notifications"
-msgstr ""
+msgstr "Notificacions"
#: idhub/templates/templates/musician/dashboard.html:33
msgid "There is no notifications at this time."
-msgstr ""
+msgstr "No hi ha notificacions en aquest moment."
#: idhub/templates/templates/musician/dashboard.html:40
msgid "Your domains and websites"
-msgstr ""
+msgstr "Els vostres dominis i llocs web"
#: idhub/templates/templates/musician/dashboard.html:41
msgid "Dashboard page description."
-msgstr ""
+msgstr "Descripció de la pàgina del tauler."
#: idhub/templates/templates/musician/dashboard.html:56
msgid "view configuration"
-msgstr ""
+msgstr "veure la configuració"
#: idhub/templates/templates/musician/dashboard.html:63
msgid "Expiration date"
-msgstr ""
+msgstr "Data de caducitat"
#: idhub/templates/templates/musician/dashboard.html:70
msgid "Mail"
-msgstr ""
+msgstr "Correu"
#: idhub/templates/templates/musician/dashboard.html:73
msgid "mail addresses created"
-msgstr ""
+msgstr "adreces de correu creades"
#: idhub/templates/templates/musician/dashboard.html:78
msgid "Mail list"
-msgstr ""
+msgstr "Llista de correu"
#: idhub/templates/templates/musician/dashboard.html:83
msgid "Software as a Service"
-msgstr ""
+msgstr "Programari com a servei"
#: idhub/templates/templates/musician/dashboard.html:85
msgid "Nothing installed"
-msgstr ""
+msgstr "No hi ha res instal·lat"
#: idhub/templates/templates/musician/dashboard.html:90
msgid "Disk usage"
-msgstr ""
+msgstr "Ús del disc"
#: idhub/templates/templates/musician/dashboard.html:107
msgid "Configuration details"
-msgstr ""
+msgstr "Detalls de configuració"
#: idhub/templates/templates/musician/dashboard.html:114
msgid "FTP access:"
-msgstr ""
+msgstr "Accés FTP:"
#. Translators: domain configuration detail modal
#: idhub/templates/templates/musician/dashboard.html:116
msgid "Contact with the support team to get details concerning FTP access."
msgstr ""
+"Contacta amb l'equip d'assistència per obtenir detalls sobre l'accés FTP."
#: idhub/templates/templates/musician/dashboard.html:125
msgid "No website configured."
-msgstr ""
+msgstr "No s'ha configurat cap lloc web."
#: idhub/templates/templates/musician/dashboard.html:127
msgid "Root directory:"
-msgstr ""
+msgstr "Directori arrel:"
#: idhub/templates/templates/musician/dashboard.html:128
msgid "Type:"
-msgstr ""
+msgstr "Tipus:"
#: idhub/templates/templates/musician/dashboard.html:133
msgid "View DNS records"
-msgstr ""
+msgstr "Veure registres DNS"
#: idhub/templates/templates/musician/databases.html:21
msgid "associated to"
-msgstr ""
+msgstr "associada a"
#: idhub/templates/templates/musician/databases.html:34
msgid "No users for this database."
-msgstr ""
+msgstr "No hi ha usuaris per a aquesta base de dades."
#: idhub/templates/templates/musician/databases.html:45
msgid "Open database manager"
-msgstr ""
+msgstr "Obriu el gestor de bases de dades"
#. Translators: database page when there isn't any database.
#. Translators: saas page when there isn't any saas.
#: idhub/templates/templates/musician/databases.html:58
#: idhub/templates/templates/musician/saas.html:49
msgid "Ooops! Looks like there is nothing here!"
-msgstr ""
+msgstr "Vaja! Sembla que aquí no hi ha res!"
#: idhub/templates/templates/musician/domain_detail.html:5
msgid "Go back"
-msgstr ""
+msgstr "Torna enrere"
#: idhub/templates/templates/musician/domain_detail.html:7
msgid "DNS settings for"
-msgstr ""
+msgstr "Configuració de DNS per a"
#: idhub/templates/templates/musician/domain_detail.html:8
msgid "DNS settings page description."
-msgstr ""
+msgstr "Descripció de la pàgina de configuració de DNS."
#: idhub/templates/templates/musician/domain_detail.html:18
msgid "Value"
-msgstr ""
+msgstr "Valor"
#: idhub/templates/templates/musician/mail_base.html:6
#: idhub/templates/templates/musician/mailinglists.html:6
msgid "Go to global"
-msgstr ""
+msgstr "Anar a global"
#: idhub/templates/templates/musician/mail_base.html:10
#: idhub/templates/templates/musician/mailinglists.html:9
msgid "for"
-msgstr ""
+msgstr "per"
#: idhub/templates/templates/musician/mail_base.html:18
#: idhub/templates/templates/musician/mailboxes.html:16
msgid "Addresses"
-msgstr ""
+msgstr "Adreces"
#: idhub/templates/templates/musician/mailbox_change_password.html:5
#: idhub/templates/templates/musician/mailbox_form.html:24
msgid "Change password"
-msgstr ""
+msgstr "Canviar la contrasenya"
#: idhub/templates/templates/musician/mailbox_check_delete.html:7
#, python-format
msgid "Are you sure that you want remove the mailbox: \"%(name)s\"?"
-msgstr ""
+msgstr "Esteu segur que voleu eliminar la bústia de correu: \"%(name)s\"?"
#: idhub/templates/templates/musician/mailbox_check_delete.html:9
msgid ""
"All mailbox's messages will be deleted and cannot be recovered"
"strong>."
msgstr ""
+"Tots els missatges de la bústia se suprimiran i no es podran "
+"recuperar ."
#: idhub/templates/templates/musician/mailbox_form.html:9
msgid "Warning!"
-msgstr ""
+msgstr "Atenció!"
#: idhub/templates/templates/musician/mailbox_form.html:9
msgid ""
"You have reached the limit of mailboxes of your subscription so "
"extra fees may apply."
msgstr ""
+"Heu arribat al límit de bústies de la vostra subscripció, de manera que es "
+"poden aplicar càrrecs addicionals ."
#: idhub/templates/templates/musician/mailbox_form.html:10
msgid "Close"
-msgstr ""
+msgstr "Tanca"
#: idhub/templates/templates/musician/mailboxes.html:15
msgid "Filtering"
-msgstr ""
+msgstr "Filtratge"
#: idhub/templates/templates/musician/mailboxes.html:27
msgid "Update password"
-msgstr ""
+msgstr "Actualitzar la contrasenya"
#: idhub/templates/templates/musician/mailboxes.html:43
msgid "New mailbox"
-msgstr ""
+msgstr "Nova bústia"
#: idhub/templates/templates/musician/mailinglists.html:34
msgid "Active"
-msgstr ""
+msgstr "Actiu"
#: idhub/templates/templates/musician/mailinglists.html:36
msgid "Inactive"
-msgstr ""
+msgstr "Inactiu"
#: idhub/templates/templates/musician/profile.html:6
msgid "Profile"
-msgstr ""
+msgstr "Perfil"
#: idhub/templates/templates/musician/profile.html:7
msgid "Little description on profile page."
-msgstr ""
+msgstr "Petita descripció a la pàgina de perfil."
#: idhub/templates/templates/musician/profile.html:11
msgid "User information"
-msgstr ""
+msgstr "Informació d'usuari"
#: idhub/templates/templates/musician/profile.html:21
msgid "Preferred language:"
-msgstr ""
+msgstr "Idioma preferit:"
#: idhub/templates/templates/musician/profile.html:35
msgid "Billing information"
-msgstr ""
+msgstr "Dades de facturació"
#: idhub/templates/templates/musician/profile.html:49
msgid "payment method:"
-msgstr ""
+msgstr "mètode de pagament:"
#: idhub/templates/templates/musician/profile.html:60
msgid "Check your last bills"
-msgstr ""
+msgstr "Comproveu les vostres últimes factures"
#: idhub/templates/templates/musician/saas.html:18
msgid "Installed on"
-msgstr ""
+msgstr "Instal·lat a"
#: idhub/templates/templates/musician/saas.html:29
msgid "Service info"
-msgstr ""
+msgstr "Informació del servei"
#: idhub/templates/templates/musician/saas.html:30
msgid "active"
-msgstr ""
+msgstr "actiu"
#: idhub/templates/templates/musician/saas.html:37
msgid "Open service admin panel"
-msgstr ""
+msgstr "Obriu el tauler d'administració del servei"
#: idhub/user/views.py:21
msgid "My profile"
-msgstr ""
+msgstr "El meu perfil"
#: idhub/user/views.py:40
msgid "My personal data"
-msgstr ""
+msgstr "Les meves dades personals"
#: idhub/user/views.py:87
msgid "Credential"
-msgstr ""
+msgstr "Credencial"
#: idhub/user/views.py:123
msgid "Credential request"
-msgstr ""
+msgstr "Sol·licitar una credencial"
#: idhub/user/views.py:136
msgid "The credential was issued successfully!"
-msgstr ""
+msgstr "La credencial s'ha emès correctament!"
#: idhub/user/views.py:140
msgid "The credential does not exist!"
-msgstr ""
+msgstr "La credencial no existeix!"
#: idhub/user/views.py:146
msgid "Credential presentation"
-msgstr ""
+msgstr "Presentació de credencials"
#: idhub/user/views.py:161
msgid "The credential was presented successfully!"
-msgstr ""
+msgstr "La credencial s'ha presentat correctament!"
#: idhub/user/views.py:163
msgid "Error sending credential!"
-msgstr ""
+msgstr "S'ha produït un error en enviar la credencial!"
#: idhub/user/views.py:182
msgid "Add a new Identity (DID)"
-msgstr ""
+msgstr "Afegeix una nova identitat (DID)"
#: idhub_auth/forms.py:19 idhub_auth/forms.py:27
msgid "The string must contain only characters and spaces"
-msgstr ""
+msgstr "La cadena ha de contenir només caràcters i espais"
diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..71cbdf3
Binary files /dev/null and b/locale/es/LC_MESSAGES/django.mo differ
diff --git a/oidc4vp/__init__.py b/oidc4vp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/oidc4vp/admin.py b/oidc4vp/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/oidc4vp/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/oidc4vp/apps.py b/oidc4vp/apps.py
new file mode 100644
index 0000000..1e00f2b
--- /dev/null
+++ b/oidc4vp/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class Oidc4VpConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'oidc4vp'
diff --git a/oidc4vp/forms.py b/oidc4vp/forms.py
new file mode 100644
index 0000000..b3c4c9e
--- /dev/null
+++ b/oidc4vp/forms.py
@@ -0,0 +1,81 @@
+import json
+import requests
+
+from django import forms
+from django.conf import settings
+from django.template.loader import get_template
+from django.utils.translation import gettext_lazy as _
+from django.core.exceptions import ValidationError
+
+from utils.idhub_ssikit import create_verifiable_presentation
+from oidc4vp.models import Organization
+from idhub.models import VerificableCredential
+
+
+class AuthorizeForm(forms.Form):
+
+ def __init__(self, *args, **kwargs):
+ self.data = kwargs.get('data', {}).copy()
+ self.user = kwargs.pop('user', None)
+ self.org = kwargs.pop('org', None)
+ self.code = kwargs.pop('code', None)
+ self.presentation_definition = kwargs.pop('presentation_definition', [])
+
+ reg = r'({})'.format('|'.join(self.presentation_definition))
+
+ self.all_credentials = self.user.vcredentials.filter(
+ schema__type__iregex=reg,
+ )
+ self.credentials = self.all_credentials.filter(
+ status=VerificableCredential.Status.ISSUED.value
+ )
+ super().__init__(*args, **kwargs)
+ for vp in self.presentation_definition:
+ vp = vp.lower()
+ choices = [
+ (str(x.id), x.schema.type.lower()) for x in self.credentials.filter(
+ schema__type__iexact=vp)
+ ]
+ self.fields[vp.lower()] = forms.ChoiceField(
+ widget=forms.RadioSelect,
+ choices=choices
+ )
+ def clean(self):
+ data = super().clean()
+ self.list_credentials = []
+ for c in self.credentials:
+ if str(c.id) == data.get(c.schema.type.lower()):
+ if c.status is not c.Status.ISSUED.value or not c.data:
+ txt = _('There are some problems with this credentials')
+ raise ValidationError(txt)
+
+ self.list_credentials.append(c)
+
+ if not self.code:
+ txt = _("There isn't code in request")
+ raise ValidationError(txt)
+
+ return data
+
+ def save(self, commit=True):
+ if not self.list_credentials:
+ return
+
+ self.get_verificable_presentation()
+
+ if commit:
+ return self.org.send(self.vp, self.code)
+
+ return
+
+ def get_verificable_presentation(self):
+ did = self.list_credentials[0].subject_did
+ vp_template = get_template('credentials/verifiable_presentation.json')
+ vc_list = json.dumps([json.loads(x.data) for x in self.list_credentials])
+
+ context = {
+ "holder_did": did.did,
+ "verifiable_credential_list": vc_list
+ }
+ unsigned_vp = vp_template.render(context)
+ self.vp = create_verifiable_presentation(did.key_material, unsigned_vp)
diff --git a/oidc4vp/migrations/0001_initial.py b/oidc4vp/migrations/0001_initial.py
new file mode 100644
index 0000000..700c4e8
--- /dev/null
+++ b/oidc4vp/migrations/0001_initial.py
@@ -0,0 +1,137 @@
+# Generated by Django 4.2.5 on 2023-12-11 08:35
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import oidc4vp.models
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Authorization',
+ fields=[
+ (
+ 'id',
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'code',
+ models.CharField(default=oidc4vp.models.set_code, max_length=24),
+ ),
+ ('code_used', models.BooleanField()),
+ ('created', models.DateTimeField(auto_now=True)),
+ ('presentation_definition', models.CharField(max_length=250)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Organization',
+ fields=[
+ (
+ 'id',
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ ('name', models.CharField(max_length=250)),
+ (
+ 'client_id',
+ models.CharField(
+ default=oidc4vp.models.set_client_id, max_length=24, unique=True
+ ),
+ ),
+ (
+ 'client_secret',
+ models.CharField(
+ default=oidc4vp.models.set_client_secret, max_length=48
+ ),
+ ),
+ ('my_client_id', models.CharField(max_length=24)),
+ ('my_client_secret', models.CharField(max_length=48)),
+ (
+ 'response_uri',
+ models.URLField(
+ help_text='Url where to send the verificable presentation',
+ max_length=250,
+ ),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name='OAuth2VPToken',
+ fields=[
+ (
+ 'id',
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ ('created', models.DateTimeField(auto_now=True)),
+ ('result_verify', models.CharField(max_length=255)),
+ ('vp_token', models.TextField()),
+ (
+ 'authorization',
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='oauth2vptoken',
+ to='oidc4vp.authorization',
+ ),
+ ),
+ (
+ 'organization',
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='vp_tokens',
+ to='oidc4vp.organization',
+ ),
+ ),
+ (
+ 'user',
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='vp_tokens',
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ migrations.AddField(
+ model_name='authorization',
+ name='organization',
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='authorizations',
+ to='oidc4vp.organization',
+ ),
+ ),
+ migrations.AddField(
+ model_name='authorization',
+ name='user',
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ]
diff --git a/oidc4vp/migrations/__init__.py b/oidc4vp/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/oidc4vp/models.py b/oidc4vp/models.py
new file mode 100644
index 0000000..66ceff1
--- /dev/null
+++ b/oidc4vp/models.py
@@ -0,0 +1,218 @@
+import json
+import requests
+import secrets
+
+from django.conf import settings
+from django.http import QueryDict
+from django.utils.translation import gettext_lazy as _
+from django.shortcuts import get_object_or_404
+from idhub_auth.models import User
+from django.db import models
+from utils.idhub_ssikit import verify_presentation
+
+
+SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+
+def gen_salt(length: int) -> str:
+ """Generate a random string of SALT_CHARS with specified ``length``."""
+ if length <= 0:
+ raise ValueError("Salt length must be positive")
+
+ return "".join(secrets.choice(SALT_CHARS) for _ in range(length))
+
+
+def set_client_id():
+ return gen_salt(24)
+
+
+def set_client_secret():
+ return gen_salt(48)
+
+
+def set_code():
+ return gen_salt(24)
+
+
+class Organization(models.Model):
+ """
+ This class represent a member of one net trust or federated host.
+ Client_id and client_secret are the credentials of this organization
+ get a connection to my. (receive a request)
+ My_client_id and my_client_secret are my credentials than to use if I
+ want to connect to this organization. (send a request)
+ For use the packages requests we need use my_client_id
+ For use in the get or post method of a View, then we need use client_id
+ and secret_id
+ """
+ name = models.CharField(max_length=250)
+ client_id = models.CharField(
+ max_length=24,
+ default=set_client_id,
+ unique=True
+ )
+ client_secret = models.CharField(
+ max_length=48,
+ default=set_client_secret
+ )
+ my_client_id = models.CharField(
+ max_length=24,
+ )
+ my_client_secret = models.CharField(
+ max_length=48,
+ )
+ response_uri = models.URLField(
+ help_text=_("Url where to send the verificable presentation"),
+ max_length=250
+ )
+
+ def send(self, vp, code):
+ """
+ Send the verificable presentation to Verifier
+ """
+ url = "{url}/verify".format(
+ url=self.response_uri.strip("/"),
+ )
+ auth = (self.my_client_id, self.my_client_secret)
+ data = {"vp_token": vp}
+ if code:
+ data["code"] = code
+
+ return requests.post(url, data=data, auth=auth)
+
+ def demand_authorization(self):
+ """
+ Send the a request for start a process of Verifier
+ """
+ url = "{url}/verify?demand_uri={redirect_uri}".format(
+ url=self.response_uri.strip("/"),
+ redirect_uri=settings.RESPONSE_URI
+ )
+ auth = (self.my_client_id, self.my_client_secret)
+ return requests.get(url, auth=auth)
+
+ def __str__(self):
+ return self.name
+
+
+###################
+# Verifier clases #
+###################
+
+
+class Authorization(models.Model):
+ """
+ This class represent a query through browser the client to the wallet.
+ The Verifier need to do a redirection to the user to Wallet.
+ The code we use as a soft foreing key between Authorization and OAuth2VPToken.
+ """
+ code = models.CharField(max_length=24, default=set_code)
+ code_used = models.BooleanField(default=False)
+ created = models.DateTimeField(auto_now=True)
+ presentation_definition = models.CharField(max_length=250)
+ organization = models.ForeignKey(
+ Organization,
+ on_delete=models.CASCADE,
+ related_name='authorizations',
+ null=True,
+ )
+ user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ null=True,
+ )
+
+ def authorize(self, path=None):
+ data = {
+ "response_type": "vp_token",
+ "response_mode": "direct_post",
+ "client_id": self.organization.my_client_id,
+ "presentation_definition": self.presentation_definition,
+ "code": self.code,
+ "nonce": gen_salt(5),
+ }
+ query_dict = QueryDict('', mutable=True)
+ query_dict.update(data)
+
+ response_uri = self.organization.response_uri.strip("/")
+ if path:
+ response_uri = "{}/{}".format(response_uri, path.strip("/"))
+
+ url = '{response_uri}/authorize?{params}'.format(
+ response_uri=response_uri,
+ params=query_dict.urlencode()
+ )
+ return url
+
+
+class OAuth2VPToken(models.Model):
+ """
+ This class represent the response of Wallet to Verifier
+ and the result of verify.
+ """
+ created = models.DateTimeField(auto_now=True)
+ result_verify = models.CharField(max_length=255)
+ vp_token = models.TextField()
+ organization = models.ForeignKey(
+ Organization,
+ on_delete=models.CASCADE,
+ related_name='vp_tokens',
+ null=True,
+ )
+ user = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ related_name='vp_tokens',
+ null=True,
+ )
+ authorization = models.ForeignKey(
+ Authorization,
+ on_delete=models.SET_NULL,
+ related_name='vp_tokens',
+ null=True,
+ )
+
+ def __init__(self, *args, **kwargs):
+ code = kwargs.pop("code", None)
+ super().__init__(*args, **kwargs)
+
+ self.authorization = Authorization.objects.filter(code=code).first()
+
+ def verifing(self):
+ self.result_verify = verify_presentation(self.vp_token)
+
+ def get_response_verify(self):
+ response = {
+ "verify": ',',
+ "redirect_uri": "",
+ "response": "",
+ }
+ verification = json.loads(self.result_verify)
+ if verification.get('errors') or verification.get('warnings'):
+ response["verify"] = "Error, Verification Failed"
+ return response
+
+ response["verify"] = "Ok, Verification correct"
+ response["redirect_uri"] = self.get_redirect_url()
+ return response
+
+ def get_redirect_url(self):
+ data = {
+ "code": self.authorization.code,
+ }
+ query_dict = QueryDict('', mutable=True)
+ query_dict.update(data)
+
+ response_uri = settings.ALLOW_CODE_URI
+
+ url = '{response_uri}?{params}'.format(
+ response_uri=response_uri,
+ params=query_dict.urlencode()
+ )
+ return url
+
+ def get_user_info(self):
+ tk = json.loads(self.vp_token)
+ self.user_info = tk.get(
+ "verifiableCredential", [{}]
+ )[-1].get("credentialSubject")
diff --git a/oidc4vp/templates/credentials/verifiable_presentation.json b/oidc4vp/templates/credentials/verifiable_presentation.json
new file mode 100644
index 0000000..a55b769
--- /dev/null
+++ b/oidc4vp/templates/credentials/verifiable_presentation.json
@@ -0,0 +1,11 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "http://example.org/presentations/3731",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "{{ holder_did }}",
+ "verifiableCredential": {{ verifiable_credential_list|safe }}
+}
diff --git a/oidc4vp/templates/credentials_presentation.html b/oidc4vp/templates/credentials_presentation.html
new file mode 100644
index 0000000..63f54ba
--- /dev/null
+++ b/oidc4vp/templates/credentials_presentation.html
@@ -0,0 +1,96 @@
+{% extends "idhub/base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+
+ {{ subtitle }}
+
+{% load django_bootstrap5 %}
+
+
+
+
+
+
+
+
+ {% trans 'Are you sure that you want delete this user?' %}
+
+
+
+
+
+{% endblock %}
diff --git a/oidc4vp/tests.py b/oidc4vp/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/oidc4vp/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/oidc4vp/urls.py b/oidc4vp/urls.py
new file mode 100644
index 0000000..d7b79be
--- /dev/null
+++ b/oidc4vp/urls.py
@@ -0,0 +1,16 @@
+from django.urls import path, reverse_lazy
+
+from oidc4vp import views
+
+
+app_name = 'oidc4vp'
+
+
+urlpatterns = [
+ path('verify', views.VerifyView.as_view(),
+ name="verify"),
+ path('authorize', views.AuthorizeView.as_view(),
+ name="authorize"),
+ path('allow_code', views.AllowCodeView.as_view(),
+ name="allow_code"),
+]
diff --git a/oidc4vp/views.py b/oidc4vp/views.py
new file mode 100644
index 0000000..a62e462
--- /dev/null
+++ b/oidc4vp/views.py
@@ -0,0 +1,165 @@
+import json
+import base64
+
+from django.conf import settings
+from django.views.generic.edit import View, FormView
+from django.http import HttpResponse, Http404, JsonResponse
+from django.shortcuts import get_object_or_404, redirect
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.decorators import method_decorator
+from django.utils.translation import gettext_lazy as _
+from django.urls import reverse_lazy
+from django.contrib import messages
+
+from oidc4vp.models import Authorization, Organization, OAuth2VPToken
+from idhub.mixins import UserView
+
+from oidc4vp.forms import AuthorizeForm
+from utils.idhub_ssikit import verify_presentation
+
+
+class AuthorizeView(UserView, FormView):
+ title = _("My wallet")
+ section = "MyWallet"
+ template_name = "credentials_presentation.html"
+ subtitle = _('Credential presentation')
+ icon = 'bi bi-patch-check-fill'
+ form_class = AuthorizeForm
+ success_url = reverse_lazy('idhub:user_demand_authorization')
+
+ def get(self, request, *args, **kwargs):
+ response = super().get(request, *args, **kwargs)
+ if self.request.session.get('next_url'):
+ return redirect(reverse_lazy('idhub:user_credentials_request'))
+ return response
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs['user'] = self.request.user
+ try:
+ vps = json.loads(self.request.GET.get('presentation_definition'))
+ except:
+ vps = []
+ kwargs['presentation_definition'] = vps
+ kwargs["org"] = self.get_org()
+ kwargs["code"] = self.request.GET.get('code')
+ return kwargs
+
+ def get_form(self, form_class=None):
+ form = super().get_form(form_class=form_class)
+ if form.all_credentials.exists() and not form.credentials.exists():
+ self.request.session['next_url'] = self.request.get_full_path()
+ return form
+
+ def form_valid(self, form):
+ authorization = form.save()
+ if not authorization or authorization.status_code != 200:
+ messages.error(self.request, _("Error sending credential!"))
+ return super().form_valid(form)
+ try:
+ authorization = authorization.json()
+ except:
+ messages.error(self.request, _("Error sending credential!"))
+ return super().form_valid(form)
+
+ verify = authorization.get('verify')
+ result, msg = verify.split(",")
+ if 'error' in result.lower():
+ messages.error(self.request, msg)
+ if 'ok' in result.lower():
+ messages.success(self.request, msg)
+
+ if authorization.get('redirect_uri'):
+ return redirect(authorization.get('redirect_uri'))
+ elif authorization.get('response'):
+ txt = authorization.get('response')
+ messages.success(self.request, txt)
+
+ return super().form_valid(form)
+
+ def get_org(self):
+ client_id = self.request.GET.get("client_id")
+ if not client_id:
+ raise Http404("Organization not found!")
+
+ org = get_object_or_404(
+ Organization,
+ client_id=client_id,
+ )
+ return org
+
+
+@method_decorator(csrf_exempt, name='dispatch')
+class VerifyView(View):
+ def get(self, request, *args, **kwargs):
+ org = self.validate(request)
+ presentation_definition = json.dumps(settings.SUPPORTED_CREDENTIALS)
+ authorization = Authorization(
+ organization=org,
+ presentation_definition=presentation_definition
+ )
+ authorization.save()
+ res = json.dumps({"redirect_uri": authorization.authorize()})
+ return HttpResponse(res)
+
+ def post(self, request, *args, **kwargs):
+ code = self.request.POST.get("code")
+ vp_tk = self.request.POST.get("vp_token")
+
+ if not vp_tk or not code:
+ raise Http404("Page not Found!")
+
+ org = self.validate(request)
+
+ vp_token = OAuth2VPToken(
+ vp_token = vp_tk,
+ organization=org,
+ code=code
+ )
+ if not vp_token.authorization:
+ raise Http404("Page not Found!")
+
+ vp_token.verifing()
+ response = vp_token.get_response_verify()
+ vp_token.save()
+ if not vp_token.authorization.promotions.exists():
+ response["redirect_uri"] = ""
+ response["response"] = "Validation Code {}".format(code)
+
+ return JsonResponse(response)
+
+ def validate(self, request):
+ auth_header = request.headers.get('Authorization', b'')
+ auth_data = auth_header.split()
+
+ if len(auth_data) == 2 and auth_data[0].lower() == 'basic':
+ decoded_auth = base64.b64decode(auth_data[1]).decode('utf-8')
+ client_id, client_secret = decoded_auth.split(':', 1)
+ org_url = request.GET.get('demand_uri')
+ org = get_object_or_404(
+ Organization,
+ client_id=client_id,
+ client_secret=client_secret
+ )
+ return org
+
+ raise Http404("Page not Found!")
+
+
+class AllowCodeView(View):
+ def get(self, request, *args, **kwargs):
+ code = self.request.GET.get("code")
+
+ if not code:
+ raise Http404("Page not Found!")
+ self.authorization = get_object_or_404(
+ Authorization,
+ code=code,
+ code_used=False
+ )
+ if not self.authorization.promotions.exists():
+ raise Http404("Page not Found!")
+
+ promotion = self.authorization.promotions.all()[0]
+ return redirect(promotion.get_url(code))
+
diff --git a/promotion/__init__.py b/promotion/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/promotion/admin.py b/promotion/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/promotion/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/promotion/apps.py b/promotion/apps.py
new file mode 100644
index 0000000..baccc02
--- /dev/null
+++ b/promotion/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class PromotionConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'promotion'
diff --git a/promotion/forms.py b/promotion/forms.py
new file mode 100644
index 0000000..5b96423
--- /dev/null
+++ b/promotion/forms.py
@@ -0,0 +1,65 @@
+
+import json
+import requests
+
+from django import forms
+from django.conf import settings
+from django.template.loader import get_template
+from django.utils.translation import gettext_lazy as _
+from django.core.exceptions import ValidationError
+
+from utils.idhub_ssikit import create_verifiable_presentation
+from oidc4vp.models import Organization, Authorization
+from promotion.models import Promotion
+
+
+class WalletForm(forms.Form):
+ organization = forms.ChoiceField(choices=[])
+
+ def __init__(self, *args, **kwargs):
+ self.presentation_definition = kwargs.pop('presentation_definition', [])
+ super().__init__(*args, **kwargs)
+ self.fields['organization'].choices = [
+ (x.id, x.name) for x in Organization.objects.filter()
+ if x.response_uri != settings.RESPONSE_URI
+ ]
+
+ def save(self, commit=True):
+ self.org = Organization.objects.filter(
+ id=self.data['organization']
+ )
+ if not self.org.exists():
+ return
+
+ self.org = self.org[0]
+
+ self.authorization = Authorization(
+ organization=self.org,
+ presentation_definition=self.presentation_definition,
+ )
+ self.promotion = Promotion(
+ discount = Promotion.Types.VULNERABLE.value,
+ authorize = self.authorization
+ )
+
+ if commit:
+ self.authorization.save()
+ self.promotion.save()
+
+ return self.authorization.authorize()
+
+ return
+
+
+class ContractForm(forms.Form):
+ nif = forms.CharField()
+ name = forms.CharField()
+ first_last_name = forms.CharField()
+ second_last_name = forms.CharField()
+ email = forms.CharField()
+ email_repeat = forms.CharField()
+ telephone = forms.CharField()
+ birthday = forms.CharField()
+ gen = forms.CharField()
+ lang = forms.CharField()
+
diff --git a/promotion/migrations/0001_initial.py b/promotion/migrations/0001_initial.py
new file mode 100644
index 0000000..cbc1f17
--- /dev/null
+++ b/promotion/migrations/0001_initial.py
@@ -0,0 +1,45 @@
+# Generated by Django 4.2.5 on 2023-12-11 08:35
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = [
+ ('oidc4vp', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Promotion',
+ fields=[
+ (
+ 'id',
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ ('name', models.CharField(max_length=250)),
+ (
+ 'discount',
+ models.PositiveSmallIntegerField(
+ choices=[(1, 'Financial vulnerability')]
+ ),
+ ),
+ (
+ 'authorize',
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='promotions',
+ to='oidc4vp.authorization',
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/promotion/migrations/__init__.py b/promotion/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/promotion/models.py b/promotion/models.py
new file mode 100644
index 0000000..4401faf
--- /dev/null
+++ b/promotion/models.py
@@ -0,0 +1,31 @@
+from django.db import models
+from django.urls import reverse_lazy
+from django.utils.translation import gettext_lazy as _
+from oidc4vp.models import Authorization
+
+
+class Promotion(models.Model):
+ class Types(models.IntegerChoices):
+ VULNERABLE = 1, _("Financial vulnerability")
+
+ name = models.CharField(max_length=250)
+ discount = models.PositiveSmallIntegerField(
+ choices=Types.choices,
+ )
+ authorize = models.ForeignKey(
+ Authorization,
+ on_delete=models.CASCADE,
+ related_name='promotions',
+ null=True,
+ )
+
+ def get_url(self, code):
+ url = "{}?code={}".format(
+ reverse_lazy("promotion:contract"),
+ code
+ )
+ return url
+
+ def get_discount(self, price):
+ return price - price*0.25
+
diff --git a/promotion/templates/select_wallet.html b/promotion/templates/select_wallet.html
new file mode 100644
index 0000000..ba813ee
--- /dev/null
+++ b/promotion/templates/select_wallet.html
@@ -0,0 +1,1126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Escull la teva tarifa mòbil - Som Connexió
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Som Connexió és una cooperativa de consum en la que tots els usuaris són socis.
+
+
+
+
+
+
+
+
+skip to Main Content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% load i18n %}
+{% load django_bootstrap5 %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Back To Top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Si tens dubtes sobre les tarifes o necessites ajuda per contractar, deixa’ns les teves dades i et trucarem en l’horari que hagis indicat.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Et trucarem en l’horari que hagis indicat, màxim 2 dies laborables després d’enviar-nos aquesta sol·licitud. Si tens una incidència de servei, truca a emergències al 693 761 723.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
Aquesta pàgina web utilitza galetes pròpies i de tercers que emmagatzemen i recuperen informació sobre la teva navegació amb finalitats estadístiques; de personalització de la teva sessió d’usuària o de la forma com se’t mostren els continguts; i, per a garantir el correcte funcionament del web. Pots acceptar o configurar el seu ús, fins i tot per a rebutjar-les, mitjançant el botó "Opcions".
+
+
+
+ ACCEPTAR
+ REBUTJAR
+ OPCIONS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tanca els paràmetres de les galetes RGPD
+
+
+
+
+
+
+
+
+
+
+ Configuració de les cookies
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Per obtenir més informació sobre l’ús de galetes, consulta la nostra Política de privadesa i Política de cookies .
+
+
+
+
+
+
+
Funcionals i estrictament necessàries
+
+
Utilitzem cookies funcionals per tal de personalitzar el nostre lloc web segons les preferències de les persones usuàries i són necessàries per al correcte funcionament del nostre lloc web.
+
+
+
+
+
+
+
+
+
+
Analítiques
+
+
Utilitzem cookies analítiques pròpies i de tercers per recopilar informació sobre la manera com les persones utilitzen el nostre lloc web.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
Publicitàries
+
+
Les galetes publicitàries ens permeten fer un seguiment del que els usuaris estan veient i amb el que estan interactuant al lloc web. Mitjançant les mateixes galetes, compartim aquesta informació amb socis comercials i de mitjans amb què treballem, els quals la poden utilitzar per construir un perfil d'interessos i després mostrar publicitat rellevant en altres llocs web que visitis.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/promotion/templates/somconnexio_contract.html b/promotion/templates/somconnexio_contract.html
new file mode 100644
index 0000000..7b7779e
--- /dev/null
+++ b/promotion/templates/somconnexio_contract.html
@@ -0,0 +1,1170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Escull la teva tarifa mòbil - Som Connexió
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Som Connexió és una cooperativa de consum en la que tots els usuaris són socis.
+
+
+
+
+
+
+
+
+skip to Main Content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tarifes Mòbil - Contractar
+
+
+{% load i18n %}
+{% load django_bootstrap5 %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Back To Top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Si tens dubtes sobre les tarifes o necessites ajuda per contractar, deixa’ns les teves dades i et trucarem en l’horari que hagis indicat.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Et trucarem en l’horari que hagis indicat, màxim 2 dies laborables després d’enviar-nos aquesta sol·licitud. Si tens una incidència de servei, truca a emergències al 693 761 723.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
Aquesta pàgina web utilitza galetes pròpies i de tercers que emmagatzemen i recuperen informació sobre la teva navegació amb finalitats estadístiques; de personalització de la teva sessió d’usuària o de la forma com se’t mostren els continguts; i, per a garantir el correcte funcionament del web. Pots acceptar o configurar el seu ús, fins i tot per a rebutjar-les, mitjançant el botó "Opcions".
+
+
+
+ ACCEPTAR
+ REBUTJAR
+ OPCIONS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tanca els paràmetres de les galetes RGPD
+
+
+
+
+
+
+
+
+
+
+ Configuració de les cookies
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Per obtenir més informació sobre l’ús de galetes, consulta la nostra Política de privadesa i Política de cookies .
+
+
+
+
+
+
+
Funcionals i estrictament necessàries
+
+
Utilitzem cookies funcionals per tal de personalitzar el nostre lloc web segons les preferències de les persones usuàries i són necessàries per al correcte funcionament del nostre lloc web.
+
+
+
+
+
+
+
+
+
+
Analítiques
+
+
Utilitzem cookies analítiques pròpies i de tercers per recopilar informació sobre la manera com les persones utilitzen el nostre lloc web.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
Publicitàries
+
+
Les galetes publicitàries ens permeten fer un seguiment del que els usuaris estan veient i amb el que estan interactuant al lloc web. Mitjançant les mateixes galetes, compartim aquesta informació amb socis comercials i de mitjans amb què treballem, els quals la poden utilitzar per construir un perfil d'interessos i després mostrar publicitat rellevant en altres llocs web que visitis.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/promotion/templates/somconnexio_tarifes_mobil.html b/promotion/templates/somconnexio_tarifes_mobil.html
new file mode 100644
index 0000000..3016e59
--- /dev/null
+++ b/promotion/templates/somconnexio_tarifes_mobil.html
@@ -0,0 +1,1219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Escull la teva tarifa mòbil - Som Connexió
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Som Connexió és una cooperativa de consum en la que tots els usuaris són socis.
+
+
+
+
+
+
+
+
+skip to Main Content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Back To Top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Si tens dubtes sobre les tarifes o necessites ajuda per contractar, deixa’ns les teves dades i et trucarem en l’horari que hagis indicat.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Et trucarem en l’horari que hagis indicat, màxim 2 dies laborables després d’enviar-nos aquesta sol·licitud. Si tens una incidència de servei, truca a emergències al 693 761 723.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
Aquesta pàgina web utilitza galetes pròpies i de tercers que emmagatzemen i recuperen informació sobre la teva navegació amb finalitats estadístiques; de personalització de la teva sessió d’usuària o de la forma com se’t mostren els continguts; i, per a garantir el correcte funcionament del web. Pots acceptar o configurar el seu ús, fins i tot per a rebutjar-les, mitjançant el botó "Opcions".
+
+
+
+ ACCEPTAR
+ REBUTJAR
+ OPCIONS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tanca els paràmetres de les galetes RGPD
+
+
+
+
+
+
+
+
+
+
+ Configuració de les cookies
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Per obtenir més informació sobre l’ús de galetes, consulta la nostra Política de privadesa i Política de cookies .
+
+
+
+
+
+
+
Funcionals i estrictament necessàries
+
+
Utilitzem cookies funcionals per tal de personalitzar el nostre lloc web segons les preferències de les persones usuàries i són necessàries per al correcte funcionament del nostre lloc web.
+
+
+
+
+
+
+
+
+
+
Analítiques
+
+
Utilitzem cookies analítiques pròpies i de tercers per recopilar informació sobre la manera com les persones utilitzen el nostre lloc web.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
Publicitàries
+
+
Les galetes publicitàries ens permeten fer un seguiment del que els usuaris estan veient i amb el que estan interactuant al lloc web. Mitjançant les mateixes galetes, compartim aquesta informació amb socis comercials i de mitjans amb què treballem, els quals la poden utilitzar per construir un perfil d'interessos i després mostrar publicitat rellevant en altres llocs web que visitis.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/promotion/templates/somconnexio_thanks.html b/promotion/templates/somconnexio_thanks.html
new file mode 100644
index 0000000..b3c694f
--- /dev/null
+++ b/promotion/templates/somconnexio_thanks.html
@@ -0,0 +1,1223 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Escull la teva tarifa mòbil - Som Connexió
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Som Connexió és una cooperativa de consum en la que tots els usuaris són socis.
+
+
+
+
+
+
+
+
+skip to Main Content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contrato
+
+
+
+
+Contrato realizado correctamente. Te hemos enviado la información por email.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Back To Top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Si tens dubtes sobre les tarifes o necessites ajuda per contractar, deixa’ns les teves dades i et trucarem en l’horari que hagis indicat.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vols que et truquem?
+
Et trucarem en l’horari que hagis indicat, màxim 2 dies laborables després d’enviar-nos aquesta sol·licitud. Si tens una incidència de servei, truca a emergències al 693 761 723.
+
(Recorda, et trucarem des d’un número que no coneixes, agafa’ns-el)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
Aquesta pàgina web utilitza galetes pròpies i de tercers que emmagatzemen i recuperen informació sobre la teva navegació amb finalitats estadístiques; de personalització de la teva sessió d’usuària o de la forma com se’t mostren els continguts; i, per a garantir el correcte funcionament del web. Pots acceptar o configurar el seu ús, fins i tot per a rebutjar-les, mitjançant el botó "Opcions".
+
+
+
+ ACCEPTAR
+ REBUTJAR
+ OPCIONS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tanca els paràmetres de les galetes RGPD
+
+
+
+
+
+
+
+
+
+
+ Configuració de les cookies
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Per obtenir més informació sobre l’ús de galetes, consulta la nostra Política de privadesa i Política de cookies .
+
+
+
+
+
+
+
Funcionals i estrictament necessàries
+
+
Utilitzem cookies funcionals per tal de personalitzar el nostre lloc web segons les preferències de les persones usuàries i són necessàries per al correcte funcionament del nostre lloc web.
+
+
+
+
+
+
+
+
+
+
Analítiques
+
+
Utilitzem cookies analítiques pròpies i de tercers per recopilar informació sobre la manera com les persones utilitzen el nostre lloc web.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
Publicitàries
+
+
Les galetes publicitàries ens permeten fer un seguiment del que els usuaris estan veient i amb el que estan interactuant al lloc web. Mitjançant les mateixes galetes, compartim aquesta informació amb socis comercials i de mitjans amb què treballem, els quals la poden utilitzar per construir un perfil d'interessos i després mostrar publicitat rellevant en altres llocs web que visitis.
+
+
+
+
¡Por favor, activa primero las cookies estrictamente necesarias para que podamos guardar tus preferencias!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/promotion/tests.py b/promotion/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/promotion/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/promotion/urls.py b/promotion/urls.py
new file mode 100644
index 0000000..2925480
--- /dev/null
+++ b/promotion/urls.py
@@ -0,0 +1,18 @@
+from django.urls import path, reverse_lazy
+
+from promotion import views
+
+
+app_name = 'promotion'
+
+
+urlpatterns = [
+ path('', views.PromotionView.as_view(),
+ name="show_promotion"),
+ path('select_wallet', views.SelectWalletView.as_view(),
+ name="select_wallet"),
+ path('contract', views.ContractView.as_view(),
+ name="contract"),
+ path('contract/1', views.ThanksView.as_view(),
+ name="thanks"),
+]
diff --git a/promotion/views.py b/promotion/views.py
new file mode 100644
index 0000000..79b47ea
--- /dev/null
+++ b/promotion/views.py
@@ -0,0 +1,125 @@
+import json
+
+from django.views.generic.edit import View, FormView
+from django.shortcuts import redirect
+from django.template.loader import get_template
+from django.urls import reverse_lazy
+from django.http import HttpResponse
+
+from oidc4vp.models import Authorization
+from promotion.forms import WalletForm, ContractForm
+
+
+class PromotionView(View):
+ template_name = "somconnexio_tarifes_mobil.html"
+ def get(self, request, *args, **kwargs):
+ self.context = {}
+ template = get_template(
+ self.template_name,
+ ).render()
+ return HttpResponse(template)
+
+class ThanksView(View):
+ template_name = "somconnexio_thanks.html"
+ def get(self, request, *args, **kwargs):
+ self.context = {}
+ template = get_template(
+ self.template_name,
+ ).render()
+ return HttpResponse(template)
+
+
+class ContractView(FormView):
+ template_name = "somconnexio_contract.html"
+ promotion = None
+ vp_token = None
+ authorization = None
+ form_class = ContractForm
+ success_url = reverse_lazy('promotion:thanks')
+
+ def get_context_data(self, **kwargs):
+ self.context = super().get_context_data(**kwargs)
+ code = self.request.GET.get("code")
+ self.get_discount(code)
+ self.context.update({
+ "promotion": self.promotion,
+ "verificable_presentation": self.vp_token,
+ "sim": 10.0,
+ "mensual": 15.0,
+ "total": 25.0
+ })
+ if self.promotion:
+ self.context['sim'] = self.promotion.get_discount(self.context["sim"])
+ self.context['mensual'] = self.promotion.get_discount(self.context["mensual"])
+ self.context['total'] = self.promotion.get_discount(self.context["total"])
+
+ if self.vp_token:
+ self.context['verificable_presentation'] = self.vp_token
+
+ return self.context
+
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ code = self.request.GET.get("code")
+ self.get_discount(code)
+ if not self.vp_token:
+ return kwargs
+
+ self.vp_token.get_user_info()
+ kwargs['initial']["nif"] = self.vp_token.user_info.get("nif", '')
+ kwargs['initial']["name"] = self.vp_token.user_info.get("name", '')
+ kwargs['initial']["first_last_name"] = self.vp_token.user_info.get("first_last_name", '')
+ kwargs['initial']["second_last_name"] = self.vp_token.user_info.get("second_last_name", '')
+ kwargs['initial']["email"] = self.vp_token.user_info.get("email", '')
+ kwargs['initial']["email_repeat"] = self.vp_token.user_info.get("email", '')
+ kwargs['initial']["telephone"] = self.vp_token.user_info.get("telephone", '')
+ kwargs['initial']["birthday"] = self.vp_token.user_info.get("birthday", '')
+ kwargs['initial']["gen"] = self.vp_token.user_info.get("gen", '')
+ kwargs['initial']["lang"] = self.vp_token.user_info.get("lang", '')
+ return kwargs
+
+ def form_valid(self, form):
+ return super().form_valid(form)
+
+ def get_discount(self, code):
+ if not code:
+ return
+ if self.authorization:
+ return
+
+ self.authorization = Authorization.objects.filter(
+ code=code,
+ code_used=False
+ ).first()
+ if self.authorization:
+ if self.authorization.promotions.exists():
+ self.promotion = self.authorization.promotions.all()[0]
+ if self.authorization.vp_tokens.exists():
+ self.vp_token = self.authorization.vp_tokens.all()[0]
+
+
+class SelectWalletView(FormView):
+ template_name = "select_wallet.html"
+ form_class = WalletForm
+ success_url = reverse_lazy('promotion:select_wallet')
+ # def get(self, request, *args, **kwargs):
+ # self.context = {'form': fo}
+ # template = get_template(
+ # self.template_name,
+ # # context
+ # ).render()
+ # return HttpResponse(template)
+
+ # def post(self, request, *args, **kwargs):
+ # super().post(request, *args, **kwargs)
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs['presentation_definition'] = json.dumps(["MemberShipCard"])
+ return kwargs
+
+ def form_valid(self, form):
+ url = form.save()
+ return redirect(url)
+
diff --git a/requirements.txt b/requirements.txt
index c09c9d6..71d5e9e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,3 +12,5 @@ jinja2==3.1.2
jsonref==1.1.0
pyld==2.0.3
pynacl==1.5.0
+more-itertools==10.1.0
+dj-database-url==2.1.0
diff --git a/schemas/member.json b/schemas/member.json
deleted file mode 100644
index 38199be..0000000
--- a/schemas/member.json
+++ /dev/null
@@ -1,21 +0,0 @@
- {
- "$id": "https://pangea.org/schemas/member-credential-schema.json",
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "name": "MemberCredential",
- "description": "MemberCredential using JsonSchemaCredential",
- "type": "object",
- "properties": {
- "name": {
- "type": "string"
- },
- "email": {
- "type": "string",
- "format": "email"
- },
- "membershipType": {
- "type": "string",
- "enum": ["individual", "organization"]
- }
- },
- "required": ["name", "email", "membershipType"]
- }
diff --git a/trustchain_idhub/settings.py b/trustchain_idhub/settings.py
index 61d9637..b9eecc5 100644
--- a/trustchain_idhub/settings.py
+++ b/trustchain_idhub/settings.py
@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
import os
from ast import literal_eval
+from dj_database_url import parse as db_url
from pathlib import Path
from django.contrib.messages import constants as messages
@@ -72,13 +73,16 @@ INSTALLED_APPS = [
'django_bootstrap5',
'django_tables2',
'idhub_auth',
- 'idhub'
+ 'oidc4vp',
+ 'idhub',
+ 'promotion'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
@@ -110,10 +114,15 @@ WSGI_APPLICATION = 'trustchain_idhub.wsgi.application'
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': BASE_DIR / 'db.sqlite3',
- }
+ # 'default': {
+ # 'ENGINE': 'django.db.backends.sqlite3',
+ # 'NAME': BASE_DIR / 'db.sqlite3',
+ # }
+ 'default': config(
+ 'DATABASE_URL',
+ default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3'),
+ cast=db_url
+ )
# 'default': config(
# 'DATABASE_URL',
# default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3'),
@@ -178,9 +187,38 @@ MESSAGE_TAGS = {
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
-LANGUAGE_CODE="en"
+# LANGUAGE_CODE="en"
# LANGUAGE_CODE="es"
+LANGUAGE_CODE="ca"
+gettext = lambda s: s
+LANGUAGES = (
+ ('de', gettext('German')),
+ ('en', gettext('English')),
+ ('ca', gettext('Catalan')),
+)
USE_I18N = True
USE_L10N = True
AUTH_USER_MODEL = 'idhub_auth.User'
+RESPONSE_URI = config('RESPONSE_URI', default="")
+ALLOW_CODE_URI= config('ALLOW_CODE_URI', default="")
+SUPPORTED_CREDENTIALS = config(
+ 'SUPPORTED_CREDENTIALS',
+ default='[]',
+ cast=literal_eval
+)
+
+LOGGING = {
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {
+ "console": {"class": "logging.StreamHandler"},
+ },
+ "loggers": {
+ "django": {
+ "handlers": ["console"],
+ "level": "INFO",
+ },
+ }
+}
+
diff --git a/trustchain_idhub/urls.py b/trustchain_idhub/urls.py
index f2fdc05..e8468a3 100644
--- a/trustchain_idhub/urls.py
+++ b/trustchain_idhub/urls.py
@@ -24,4 +24,6 @@ from django.contrib.auth import views as auth_views
urlpatterns = [
# path('django-admin/', admin.site.urls),
path('', include('idhub.urls')),
+ path('oidc4vp/', include('oidc4vp.urls')),
+ path('promotion/', include('promotion.urls')),
]
diff --git a/utils/idhub_ssikit/README.md b/utils/idhub_ssikit/README.md
index e69de29..8443717 100644
--- a/utils/idhub_ssikit/README.md
+++ b/utils/idhub_ssikit/README.md
@@ -0,0 +1,73 @@
+# Helper routines to manage DIDs/VC/VPs
+
+This module is a wrapper around the functions exported by SpruceID's `DIDKit` framework.
+
+## DID generation and storage
+
+For now DIDs are of the kind `did:key`, with planned support for `did:web` in the near future.
+
+Creation of a DID involves two steps:
+* Generate a unique DID controller key
+* Derive a `did:key` type from the key
+
+Both must be stored in the IdHub database and linked to a `User` for later retrieval.
+
+```python
+# Use case: generate and link a new DID for an existing user
+user = request.user # ...
+
+controller_key = idhub_ssikit.generate_did_controller_key()
+did_string = idhub_ssikit.keydid_from_controller_key(controller_key)
+
+
+did = idhub.models.DID(
+ did = did_string,
+ user = user
+)
+did_controller_key = idhub.models.DIDControllerKey(
+ key_material = controller_key,
+ owner_did = did
+)
+
+did.save()
+did_controller_key.save()
+```
+
+## Verifiable Credential issuance
+
+Verifiable Credential templates are stored as Jinja2 (TBD) templates in `/schemas` folder. Please examine each template to see what data must be passed to it in order to render.
+
+The data passed to the template must at a minimum include:
+* issuer_did
+* subject_did
+* vc_id
+
+For example, in order to render `/schemas/member-credential.json`:
+
+```python
+from jinja2 import Environment, FileSystemLoader, select_autoescape
+import idhub_ssikit
+
+env = Environment(
+ loader=FileSystemLoader("vc_templates"),
+ autoescape=select_autoescape()
+)
+unsigned_vc_template = env.get_template("member-credential.json")
+
+issuer_user = request.user
+issuer_did = user.dids[0] # TODO: Django ORM pseudocode
+issuer_did_controller_key = did.keys[0] # TODO: Django ORM pseudocode
+
+data = {
+ "vc_id": "http://pangea.org/credentials/3731",
+ "issuer_did": issuer_did,
+ "subject_did": "did:web:[...]",
+ "issuance_date": "2020-08-19T21:41:50Z",
+ "subject_is_member_of": "Pangea"
+}
+signed_credential = idhub_ssikit.render_and_sign_credential(
+ unsigned_vc_template,
+ issuer_did_controller_key,
+ data
+)
+```
\ No newline at end of file
diff --git a/utils/idhub_ssikit/__init__.py b/utils/idhub_ssikit/__init__.py
index 18a5ff2..cc3e9b4 100644
--- a/utils/idhub_ssikit/__init__.py
+++ b/utils/idhub_ssikit/__init__.py
@@ -4,6 +4,7 @@ import didkit
import json
import jinja2
from django.template.backends.django import Template
+from django.template.loader import get_template
def generate_did_controller_key():
@@ -49,26 +50,71 @@ def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, vc_data
def sign_credential(unsigned_vc: str, jwk_issuer):
"""
- Signs the and unsigned credential with the provided key.
+ Signs the unsigned credential with the provided key.
+ The credential template must be rendered with all user data.
"""
async def inner():
- signed_vc = await didkit.issue_credential(
- unsigned_vc,
- '{"proofFormat": "ldp"}',
- jwk_issuer
- )
- return signed_vc
+ signed_vc = await didkit.issue_credential(
+ unsigned_vc,
+ '{"proofFormat": "ldp"}',
+ jwk_issuer
+ )
+ return signed_vc
return asyncio.run(inner())
-def verify_credential(vc, proof_options):
+def verify_credential(vc):
"""
Returns a (bool, str) tuple indicating whether the credential is valid.
If the boolean is true, the credential is valid and the second argument can be ignored.
If it is false, the VC is invalid and the second argument contains a JSON object with further information.
"""
async def inner():
- return didkit.verify_credential(vc, proof_options)
+ return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}')
return asyncio.run(inner())
+
+
+def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str:
+ async def inner():
+ unsigned_vp = vp_template.render(data)
+ signed_vp = await didkit.issue_presentation(
+ unsigned_vp,
+ '{"proofFormat": "ldp"}',
+ jwk_holder
+ )
+ return signed_vp
+
+ data = {
+ "holder_did": holder_did,
+ "verifiable_credential_list": "[" + ",".join(vc_list) + "]"
+ }
+
+ return asyncio.run(inner())
+
+
+def create_verifiable_presentation(jwk_holder: str, unsigned_vp: str) -> str:
+ async def inner():
+ signed_vp = await didkit.issue_presentation(
+ unsigned_vp,
+ '{"proofFormat": "ldp"}',
+ jwk_holder
+ )
+ return signed_vp
+
+ return asyncio.run(inner())
+
+
+def verify_presentation(vp):
+ """
+ Returns a (bool, str) tuple indicating whether the credential is valid.
+ If the boolean is true, the credential is valid and the second argument can be ignored.
+ If it is false, the VC is invalid and the second argument contains a JSON object with further information.
+ """
+ async def inner():
+ proof_options = '{"proofFormat": "ldp"}'
+ return await didkit.verify_presentation(vp, proof_options)
+
+ return asyncio.run(inner())
+