management of organization dids
This commit is contained in:
parent
6db0090bfe
commit
0d61d8c7de
17
apiregiter.py
Normal file
17
apiregiter.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import uuid
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
class Iota:
|
||||||
|
"""
|
||||||
|
Framework for simulate the comunication with IOTA DLT
|
||||||
|
"""
|
||||||
|
|
||||||
|
def issue_did(self):
|
||||||
|
u = str(uuid.uuid4()).encode()
|
||||||
|
d = hashlib.sha3_256(u).hexdigest()
|
||||||
|
did = "did:iota:{}".format(d)
|
||||||
|
return did
|
||||||
|
|
||||||
|
|
||||||
|
iota = Iota()
|
|
@ -1,34 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from idhub_auth.models import User
|
|
||||||
from idhub.models import Rol
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileForm(forms.ModelForm):
|
|
||||||
MANDATORY_FIELDS = ['first_name', 'last_name', 'email']
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ('first_name', 'last_name', 'email')
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipForm(forms.ModelForm):
|
|
||||||
MANDATORY_FIELDS = ['type']
|
|
||||||
|
|
||||||
|
|
||||||
class RolForm(forms.ModelForm):
|
|
||||||
MANDATORY_FIELDS = ['name']
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceForm(forms.ModelForm):
|
|
||||||
MANDATORY_FIELDS = ['domain', 'rol']
|
|
||||||
|
|
||||||
|
|
||||||
class UserRolForm(forms.ModelForm):
|
|
||||||
MANDATORY_FIELDS = ['service']
|
|
||||||
|
|
||||||
|
|
||||||
class SchemaForm(forms.Form):
|
|
||||||
file_template = forms.FileField()
|
|
||||||
|
|
||||||
class ImportForm(forms.Form):
|
|
||||||
file_import = forms.FileField()
|
|
|
@ -11,11 +11,12 @@ from smtplib import SMTPException
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.views.generic.edit import UpdateView, CreateView
|
from django.views.generic.edit import UpdateView, CreateView, DeleteView
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from apiregiter import iota
|
||||||
from idhub_auth.models import User
|
from idhub_auth.models import User
|
||||||
from idhub.mixins import AdminView
|
from idhub.mixins import AdminView
|
||||||
from idhub.email.views import NotifyActivateUserByEmail
|
from idhub.email.views import NotifyActivateUserByEmail
|
||||||
|
@ -29,15 +30,6 @@ from idhub.models import (
|
||||||
UserRol,
|
UserRol,
|
||||||
VerifiableCredential,
|
VerifiableCredential,
|
||||||
)
|
)
|
||||||
from idhub.admin.forms import (
|
|
||||||
ProfileForm,
|
|
||||||
MembershipForm,
|
|
||||||
RolForm,
|
|
||||||
ServiceForm,
|
|
||||||
UserRolForm,
|
|
||||||
SchemaForm,
|
|
||||||
ImportForm,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AdminDashboardView(AdminView, TemplateView):
|
class AdminDashboardView(AdminView, TemplateView):
|
||||||
|
@ -138,7 +130,6 @@ class AdminPeopleDeleteView(AdminPeopleView):
|
||||||
|
|
||||||
class AdminPeopleEditView(AdminPeopleView, UpdateView):
|
class AdminPeopleEditView(AdminPeopleView, UpdateView):
|
||||||
template_name = "idhub/admin/user_edit.html"
|
template_name = "idhub/admin/user_edit.html"
|
||||||
from_class = ProfileForm
|
|
||||||
fields = ('first_name', 'last_name', 'email')
|
fields = ('first_name', 'last_name', 'email')
|
||||||
success_url = reverse_lazy('idhub:admin_people_list')
|
success_url = reverse_lazy('idhub:admin_people_list')
|
||||||
|
|
||||||
|
@ -148,7 +139,6 @@ class AdminPeopleRegisterView(NotifyActivateUserByEmail, People, CreateView):
|
||||||
subtitle = _('People Register')
|
subtitle = _('People Register')
|
||||||
icon = 'bi bi-person'
|
icon = 'bi bi-person'
|
||||||
model = User
|
model = User
|
||||||
from_class = ProfileForm
|
|
||||||
fields = ('first_name', 'last_name', 'email')
|
fields = ('first_name', 'last_name', 'email')
|
||||||
success_url = reverse_lazy('idhub:admin_people_list')
|
success_url = reverse_lazy('idhub:admin_people_list')
|
||||||
|
|
||||||
|
@ -175,7 +165,6 @@ class AdminPeopleMembershipRegisterView(People, CreateView):
|
||||||
subtitle = _('People add membership')
|
subtitle = _('People add membership')
|
||||||
icon = 'bi bi-person'
|
icon = 'bi bi-person'
|
||||||
model = Membership
|
model = Membership
|
||||||
from_class = MembershipForm
|
|
||||||
fields = ('type', 'start_date', 'end_date')
|
fields = ('type', 'start_date', 'end_date')
|
||||||
success_url = reverse_lazy('idhub:admin_people_list')
|
success_url = reverse_lazy('idhub:admin_people_list')
|
||||||
|
|
||||||
|
@ -213,7 +202,6 @@ class AdminPeopleMembershipEditView(People, CreateView):
|
||||||
subtitle = _('People add membership')
|
subtitle = _('People add membership')
|
||||||
icon = 'bi bi-person'
|
icon = 'bi bi-person'
|
||||||
model = Membership
|
model = Membership
|
||||||
from_class = MembershipForm
|
|
||||||
fields = ('type', 'start_date', 'end_date')
|
fields = ('type', 'start_date', 'end_date')
|
||||||
success_url = reverse_lazy('idhub:admin_people_list')
|
success_url = reverse_lazy('idhub:admin_people_list')
|
||||||
|
|
||||||
|
@ -252,7 +240,6 @@ class AdminPeopleRolRegisterView(People, CreateView):
|
||||||
subtitle = _('Add Rol to User')
|
subtitle = _('Add Rol to User')
|
||||||
icon = 'bi bi-person'
|
icon = 'bi bi-person'
|
||||||
model = UserRol
|
model = UserRol
|
||||||
from_class = UserRolForm
|
|
||||||
fields = ('service',)
|
fields = ('service',)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -283,7 +270,6 @@ class AdminPeopleRolEditView(People, CreateView):
|
||||||
subtitle = _('Edit Rol to User')
|
subtitle = _('Edit Rol to User')
|
||||||
icon = 'bi bi-person'
|
icon = 'bi bi-person'
|
||||||
model = UserRol
|
model = UserRol
|
||||||
from_class = UserRolForm
|
|
||||||
fields = ('service',)
|
fields = ('service',)
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
|
@ -331,7 +317,6 @@ class AdminRolRegisterView(AccessControl, CreateView):
|
||||||
subtitle = _('Add Rol')
|
subtitle = _('Add Rol')
|
||||||
icon = ''
|
icon = ''
|
||||||
model = Rol
|
model = Rol
|
||||||
from_class = RolForm
|
|
||||||
fields = ('name',)
|
fields = ('name',)
|
||||||
success_url = reverse_lazy('idhub:admin_roles')
|
success_url = reverse_lazy('idhub:admin_roles')
|
||||||
object = None
|
object = None
|
||||||
|
@ -342,7 +327,6 @@ class AdminRolEditView(AccessControl, CreateView):
|
||||||
subtitle = _('Edit Rol')
|
subtitle = _('Edit Rol')
|
||||||
icon = ''
|
icon = ''
|
||||||
model = Rol
|
model = Rol
|
||||||
from_class = RolForm
|
|
||||||
fields = ('name',)
|
fields = ('name',)
|
||||||
success_url = reverse_lazy('idhub:admin_roles')
|
success_url = reverse_lazy('idhub:admin_roles')
|
||||||
|
|
||||||
|
@ -382,7 +366,6 @@ class AdminServiceRegisterView(AccessControl, CreateView):
|
||||||
subtitle = _('Add Service')
|
subtitle = _('Add Service')
|
||||||
icon = ''
|
icon = ''
|
||||||
model = Service
|
model = Service
|
||||||
from_class = ServiceForm
|
|
||||||
fields = ('domain', 'description', 'rol')
|
fields = ('domain', 'description', 'rol')
|
||||||
success_url = reverse_lazy('idhub:admin_services')
|
success_url = reverse_lazy('idhub:admin_services')
|
||||||
object = None
|
object = None
|
||||||
|
@ -393,7 +376,6 @@ class AdminServiceEditView(AccessControl, CreateView):
|
||||||
subtitle = _('Edit Service')
|
subtitle = _('Edit Service')
|
||||||
icon = ''
|
icon = ''
|
||||||
model = Service
|
model = Service
|
||||||
from_class = ServiceForm
|
|
||||||
fields = ('domain', 'description', 'rol')
|
fields = ('domain', 'description', 'rol')
|
||||||
success_url = reverse_lazy('idhub:admin_services')
|
success_url = reverse_lazy('idhub:admin_services')
|
||||||
|
|
||||||
|
@ -441,8 +423,8 @@ class AdminRevokeCredentialsView(Credentials):
|
||||||
icon = ''
|
icon = ''
|
||||||
|
|
||||||
|
|
||||||
class AdminWalletIdentitiesView(Credentials):
|
class AdminDidsView(Credentials):
|
||||||
template_name = "idhub/admin/wallet_identities.html"
|
template_name = "idhub/admin/dids.html"
|
||||||
subtitle = _('Organization Identities (DID)')
|
subtitle = _('Organization Identities (DID)')
|
||||||
icon = 'bi bi-patch-check-fill'
|
icon = 'bi bi-patch-check-fill'
|
||||||
wallet = True
|
wallet = True
|
||||||
|
@ -454,6 +436,76 @@ class AdminWalletIdentitiesView(Credentials):
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class AdminDidRegisterView(Credentials, CreateView):
|
||||||
|
template_name = "idhub/admin/did_register.html"
|
||||||
|
subtitle = _('Add a new Organization Identities (DID)')
|
||||||
|
icon = 'bi bi-patch-check-fill'
|
||||||
|
wallet = True
|
||||||
|
model = DID
|
||||||
|
fields = ('did', 'label')
|
||||||
|
success_url = reverse_lazy('idhub:admin_dids')
|
||||||
|
object = None
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['initial'] = {
|
||||||
|
'did': iota.issue_did()
|
||||||
|
}
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
form = super().get_form()
|
||||||
|
form.fields['did'].required = False
|
||||||
|
form.fields['did'].disabled = True
|
||||||
|
return form
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
user = form.save()
|
||||||
|
messages.success(self.request, _('DID created successfully'))
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminDidEditView(Credentials, UpdateView):
|
||||||
|
template_name = "idhub/admin/did_register.html"
|
||||||
|
subtitle = _('Organization Identities (DID)')
|
||||||
|
icon = 'bi bi-patch-check-fill'
|
||||||
|
wallet = True
|
||||||
|
model = DID
|
||||||
|
fields = ('did', 'label')
|
||||||
|
success_url = reverse_lazy('idhub:admin_dids')
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.pk = kwargs['pk']
|
||||||
|
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
form = super().get_form()
|
||||||
|
form.fields['did'].required = False
|
||||||
|
form.fields['did'].disabled = True
|
||||||
|
return form
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
user = form.save()
|
||||||
|
messages.success(self.request, _('DID created successfully'))
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminDidDeleteView(Credentials, DeleteView):
|
||||||
|
subtitle = _('Organization Identities (DID)')
|
||||||
|
icon = 'bi bi-patch-check-fill'
|
||||||
|
wallet = True
|
||||||
|
model = DID
|
||||||
|
success_url = reverse_lazy('idhub:admin_dids')
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
# import pdb; pdb.set_trace()
|
||||||
|
self.pk = kwargs['pk']
|
||||||
|
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||||
|
self.object.delete()
|
||||||
|
|
||||||
|
return redirect(self.success_url)
|
||||||
|
|
||||||
|
|
||||||
class AdminWalletCredentialsView(Credentials):
|
class AdminWalletCredentialsView(Credentials):
|
||||||
template_name = "idhub/admin/wallet_credentials.html"
|
template_name = "idhub/admin/wallet_credentials.html"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.5 on 2023-10-26 11:29
|
# Generated by Django 4.2.5 on 2023-10-26 13:46
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -225,7 +225,8 @@ class Migration(migrations.Migration):
|
||||||
verbose_name='ID',
|
verbose_name='ID',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
('did_string', models.CharField(max_length=250)),
|
('created_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('did', models.CharField(max_length=250, unique=True)),
|
||||||
('label', models.CharField(max_length=50)),
|
('label', models.CharField(max_length=50)),
|
||||||
(
|
(
|
||||||
'user',
|
'user',
|
||||||
|
|
|
@ -4,15 +4,6 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from idhub_auth.models import User
|
from idhub_auth.models import User
|
||||||
|
|
||||||
|
|
||||||
# class AppUser(models.Model):
|
|
||||||
# Ya incluye "first_name", "last_name", "email", y "date_joined" heredando de la clase User de django.
|
|
||||||
# Falta ver que más información hay que añadir a nuestros usuarios, como los roles etc.
|
|
||||||
# django_user = models.OneToOneField(DjangoUser, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
# Extra data, segun entidad/organizacion
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# class Event(models.Model):
|
# class Event(models.Model):
|
||||||
# Para los "audit logs" que se requieren en las pantallas.
|
# Para los "audit logs" que se requieren en las pantallas.
|
||||||
# timestamp = models.DateTimeField()
|
# timestamp = models.DateTimeField()
|
||||||
|
@ -21,7 +12,8 @@ from idhub_auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class DID(models.Model):
|
class DID(models.Model):
|
||||||
did_string = models.CharField(max_length=250)
|
created_at = models.DateTimeField(auto_now=True)
|
||||||
|
did = models.CharField(max_length=250, unique=True)
|
||||||
label = models.CharField(max_length=50)
|
label = models.CharField(max_length=50)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
|
|
34
idhub/templates/idhub/admin/did_register.html
Normal file
34
idhub/templates/idhub/admin/did_register.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends "idhub/base_admin.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h3>
|
||||||
|
<i class="{{ icon }}"></i>
|
||||||
|
{{ subtitle }}
|
||||||
|
</h3>
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||||
|
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
|
<div class="message">
|
||||||
|
{% for field, error in form.errors.items %}
|
||||||
|
{{ error }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions-no-box">
|
||||||
|
<a class="btn btn-grey" href="{% url 'idhub:admin_dids' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -24,15 +24,15 @@
|
||||||
<tr style="font-size:15px;">
|
<tr style="font-size:15px;">
|
||||||
<td>{{ d.created_at }}</td>
|
<td>{{ d.created_at }}</td>
|
||||||
<td>{{ d.label }}</td>
|
<td>{{ d.label }}</td>
|
||||||
<td>{{ d.id }}</td>
|
<td>{{ d.did }}</td>
|
||||||
<td><button type="button" class="btn btn-green-admin">{% trans 'Modify' %}</button></td>
|
<td><a class="text-primary" href="{% url 'idhub:admin_dids_edit' d.id %}" title="{% trans 'Edit' %}"><i class="bi bi-pencil-square"></i></a></td>
|
||||||
<td><a class="text-danger" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#confirm-delete-{{ d.id }}" title="{% trans 'Remove' %}"><i class="bi bi-x-circle"></i></a></td>
|
<td><a class="text-danger" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#confirm-delete-{{ d.id }}" title="{% trans 'Remove' %}"><i class="bi bi-x-circle"></i></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="form-actions-no-box">
|
<div class="form-actions-no-box">
|
||||||
<a class="btn btn-green-admin" href="{# url 'idhub:admin_dids_add' #}">{% translate "Add Identity" %} <i class="bi bi-plus"></i></a>
|
<a class="btn btn-green-admin" href="{% url 'idhub:admin_dids_new' %}">{% translate "Add Identity" %} <i class="bi bi-plus"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Clancel</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Clancel</button>
|
||||||
<a href="{# url 'idhub:admin_dids_del' d.id #}" type="button" class="btn btn-danger">{% trans 'Delete' %}</a>
|
<a href="{% url 'idhub:admin_dids_del' d.id %}" type="button" class="btn btn-danger">{% trans 'Delete' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -123,7 +123,7 @@
|
||||||
</a>
|
</a>
|
||||||
<ul class="flex-column mb-2 accordion-collapse {% if wallet %}expanded{% else %}collapse{% endif %}" id="lwallet" data-bs-parent="#wallet">
|
<ul class="flex-column mb-2 accordion-collapse {% if wallet %}expanded{% else %}collapse{% endif %}" id="lwallet" data-bs-parent="#wallet">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{% if path == 'admin_wallet_identities' %} active2{% endif %}" href="{% url 'idhub:admin_wallet_identities' %}">
|
<a class="nav-link{% if path == 'admin_dids' %} active2{% endif %}" href="{% url 'idhub:admin_dids' %}">
|
||||||
Identities (DID)
|
Identities (DID)
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -127,8 +127,14 @@ urlpatterns = [
|
||||||
name='admin_credentials_new'),
|
name='admin_credentials_new'),
|
||||||
path('admin/credentials/revoke/', views_admin.AdminRevokeCredentialsView.as_view(),
|
path('admin/credentials/revoke/', views_admin.AdminRevokeCredentialsView.as_view(),
|
||||||
name='admin_credentials_revoke'),
|
name='admin_credentials_revoke'),
|
||||||
path('admin/wallet/identities/', views_admin.AdminWalletIdentitiesView.as_view(),
|
path('admin/wallet/identities/', views_admin.AdminDidsView.as_view(),
|
||||||
name='admin_wallet_identities'),
|
name='admin_dids'),
|
||||||
|
path('admin/dids/new/', views_admin.AdminDidRegisterView.as_view(),
|
||||||
|
name='admin_dids_new'),
|
||||||
|
path('admin/dids/<int:pk>/', views_admin.AdminDidEditView.as_view(),
|
||||||
|
name='admin_dids_edit'),
|
||||||
|
path('admin/dids/<int:pk>/del/', views_admin.AdminDidDeleteView.as_view(),
|
||||||
|
name='admin_dids_del'),
|
||||||
path('admin/wallet/credentials/', views_admin.AdminWalletCredentialsView.as_view(),
|
path('admin/wallet/credentials/', views_admin.AdminWalletCredentialsView.as_view(),
|
||||||
name='admin_wallet_credentials'),
|
name='admin_wallet_credentials'),
|
||||||
path('admin/wallet/config/issue/', views_admin.AdminWalletConfigIssuesView.as_view(),
|
path('admin/wallet/config/issue/', views_admin.AdminWalletConfigIssuesView.as_view(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.5 on 2023-10-26 11:29
|
# Generated by Django 4.2.5 on 2023-10-26 13:46
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue