Früher oder später steht ein Django- Entwickler vor einem Problem: Wie kann man es so gestalten, dass Benutzer verschiedene Objekte desselben Typs nicht ändern, löschen oder sogar nicht sehen können?
Angenommen, in Ihrem Projekt geht es darum, Projektinformationen zu speichern. Unterschiedliche Benutzer geben unterschiedliche Projekte ein und sollten keine Informationen zu einem anderen Projekt sehen. Ein und derselbe Benutzer kann mehrere Projekte eingeben und in verschiedenen Projekten einen unterschiedlichen Status haben - irgendwo kann er nur Informationen anzeigen und in anderen - Daten bearbeiten. In einigen Projekten ist der Benutzer als Projektpersonal und in anderen nur als Verbraucher seiner Dienste registriert. Die Zugriffsebene sollte völlig unterschiedlich sein.
An diesen Problemen sind mehrere Pakete beteiligt, eines davon werden wir betrachten - Django-Access . Jeder, der interessiert ist, ist zur Katze eingeladen.
Beispiel
Betrachten Sie das in der Präambel kurz beschriebene Beispiel. Wir gehen davon aus, dass wir ein bestimmtes einfaches Datenverwaltungssystem für Projekte einer bestimmten Richtung haben, zum Beispiel für den Transport. In Bezug auf den Zugriff auf verschiedene Daten werden Systembenutzer in mehrere Kategorien unterteilt:
- Administrator - kann Daten über die Einstellung des Personals zum Projekt bearbeiten, wodurch Benutzer verwaltet, verbunden und vom Projekt getrennt werden und der Zugriffsstatus im Projekt geändert wird
- Personal - kann die Daten des gesamten Projekts verwalten: erstellen, löschen, ändern - alle Daten, außer Daten zur Einstellung des Personals zum Projekt
- Auditor - kann Projektdaten anzeigen, aber nicht ändern
- Kunde - kann nur Aufzeichnungen über die Interaktion mit ihm persönlich anzeigen sowie persönliche Daten in seinem Profil ändern
Wie man mit dem Django- Rechte-System interagiert
, Django, django.contrib.auth
. , Permission
Group
, , .
— Django django-admin startproject sample
.
Django, sample
sample
options
.
- trans
python manage.py startapp trans
.
Project
.
class Project(models.Model):
name = models.CharField(
max_length=256,
db_index=True,
verbose_name=_('Name'),
help_text=_('Name of the project')
)
def __str__(self):
return self.name
class Meta:
verbose_name = _('Project')
verbose_name_plural = _('Projects')
. ProjectMember
, .
class ProjectMember(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
verbose_name=_('User'), help_text=_('User who belongs to this project'),
related_name='projects',
)
project = models.ForeignKey(
Project, on_delete=models.CASCADE,
verbose_name=_('Project'), help_text=_('Project to which this user belongs'),
related_name='users',
)
allow_change = models.BooleanField(
default=True,
verbose_name=_('Allow Change'),
help_text=_('Is the member allowed to change the project data?')
)
allow_manage = models.BooleanField(
default=False,
verbose_name=_('Allow Manage'),
help_text=_('Is the member allowed to manage users for the project?')
)
def __str__(self):
return _('%s -> %s') % (self.user, self.project)
class Meta:
verbose_name = _('Project Member')
verbose_name_plural = _('Project Members')
unique_together = (
('user', 'project'),
)
, , . , , . , , - , .
— , — :
, , , -.
class Vehicle(models.Model):
project = models.ForeignKey(
Project, on_delete=models.CASCADE,
verbose_name=_('Project'), help_text=_('Project to which this vehicle belongs'),
related_name='vehicles',
)
code = models.CharField(
max_length=256,
db_index=True,
verbose_name=_('Code'),
help_text=_('Registration code')
)
def __str__(self):
return self.code
class Meta:
verbose_name = _('Vehicle')
verbose_name_plural = _('Vehicles')
unique_together = (
('code', 'project'),
)
class Driver(models.Model):
project = models.ForeignKey(
Project, on_delete=models.CASCADE,
verbose_name=_('Project'), help_text=_('Project to which this driver belongs'),
related_name='drivers',
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
verbose_name=_('User'), help_text=_('User who is a driver'),
related_name='driving',
)
def __str__(self):
return _('%s [%s]') % (self.user, self.project)
class Meta:
verbose_name = _('Driver')
verbose_name_plural = _('Drivers')
unique_together = (
('user', 'project'),
)
class Order(models.Model):
project = models.ForeignKey(
Project, on_delete=models.CASCADE,
verbose_name=_('Project'), help_text=_('Project to which this order belongs'),
related_name='orders',
)
client = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
verbose_name=_('Client'), help_text=_('Client getting an order'),
related_name='orders',
)
vehicle = models.ForeignKey(
Vehicle, on_delete=models.CASCADE,
verbose_name=_('Vehicle'), help_text=_('Vehicle assigned to this order'),
related_name='orders',
)
driver = models.ForeignKey(
Driver, on_delete=models.CASCADE,
verbose_name=_('Driver'), help_text=_('Driver assigned to this order'),
related_name='orders',
)
start_time = models.DateTimeField(
db_index=True,
verbose_name=_("Start Time"),
help_text=_("Time when the order is started")
)
stop_time = models.DateTimeField(
db_index=True,
verbose_name=_("Stop Time"),
help_text=_("Time when the order is stopped")
)
def __str__(self):
return _('%(start_time)s-%(stop_time)s %(client)s %(driver)s %(vehicle)s') % {
'start_time': self.start_time,
'stop_time': self.stop_time,
'client': self.client,
'driver': self.driver,
'vehicle': self.vehicle,
}
class Meta:
verbose_name = _('Order')
verbose_name_plural = _('Orders')
access
trans
settings.py, python makemigrations trans
python migrate
.
, , admin.py
trans
. Django-Access.
?. -, Django-Access , , , Django, , , .
-, , , Django-Access, , Django, .
@admin.register(Project)
class ProjectAdmin(AccessModelAdmin):
fields = ['name']
list_display = ['name']
search_fields = ['name']
@admin.register(ProjectMember)
class ProjectMemberAdmin(AccessModelAdmin):
fields = ['user', 'project', 'allow_manage', 'allow_change']
list_display = ['user', 'project', 'allow_manage', 'allow_change']
search_fields = ['user__username', 'user__first_name', 'user__last_name', 'project__name']
list_filters = ['project', 'allow_manage', 'allow_change']
autocomplete_fields = ['user', 'project']
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "project":
projects = AccessManager(Project).changeable(request)
kwargs["queryset"] = projects
return super(ProjectMemberAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Vehicle)
class VehicleAdmin(AccessModelAdmin):
fields = ['project', 'code']
list_display = ['code', 'project']
list_filters = ['project']
search_fields = ['code']
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "project":
projects = Project.objects.filter(users__user=request.user, users__allow_change=True)
kwargs["queryset"] = projects
return super(VehicleAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Driver)
class DriverAdmin(AccessModelAdmin):
fields = ['project', 'user']
list_display = ['user', 'project']
list_filters = ['project']
search_fields = ['user__username', 'user__first_name', 'user__last_name', 'project__name']
autocomplete_fields = ['user', 'project']
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "project":
projects = Project.objects.filter(users__user=request.user, users__allow_change=True)
kwargs["queryset"] = projects
return super(DriverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Order)
class OrderAdmin(AccessModelAdmin):
fields = ['project', 'client', 'vehicle', 'driver', 'start_time', 'stop_time']
list_display = ['start_time', 'stop_time', 'client', 'driver', 'vehicle', 'project']
list_filters = ['project', 'vehicle', 'driver']
search_fields = ['client__username', 'client__first_name', 'client__last_name', 'project__name',]
autocomplete_fields = ['client', 'vehicle', 'driver', 'project']
date_hierarchy = 'start_time'
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "project":
projects = Project.objects.filter(users__user=request.user, users__allow_change=True)
kwargs["queryset"] = projects
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
, .
, .
, , autocomplete:
admin.site.unregister(User)
admin.site.unregister(Group)
@admin.register(User)
class AccessUserAdmin(AccessControlMixin, UserAdmin):
def get_readonly_fields(self, request, obj=None):
readonly_fields = super(AccessUserAdmin, self).get_readonly_fields(request, obj) or []
if request.user.is_superuser:
return readonly_fields
if not obj:
return readonly_fields
restrict = ['is_superuser', 'last_login', 'date_joined']
if obj.pk != request.user.pk:
restrict = ['is_superuser', 'last_login', 'date_joined', 'password', 'email']
return [f for f in readonly_fields if f not in restrict] + restrict
def get_list_display(self, request):
fields = super(AccessUserAdmin, self).get_list_display(request) or []
if request.user.is_superuser:
return fields
restrict = ['password', 'email']
return [f for f in fields if not f in restrict]
def _fieldsets_exclude(self, fieldsets, exclude):
ret = []
for nm, params in fieldsets:
if 'fields' not in params:
ret.append((nm, params))
continue
fields = []
for f in params['fields']:
if f not in exclude:
fields.append(f)
pars = {}
pars.update(params)
pars['fields'] = fields
ret.append((nm, pars))
return ret
def _fieldsets_only(self, fieldsets, only):
ret = []
for nm, params in fieldsets:
if 'fields' not in params:
ret.append((nm, params))
continue
fields = []
for f in params['fields']:
if f in only:
fields.append(f)
pars = {}
pars.update(params)
pars['fields'] = fields
ret.append((nm, pars))
return ret
def get_fieldsets(self, request, obj=None):
fieldsets = list(super(AccessUserAdmin, self).get_fieldsets(request, obj)) or []
fields = self.get_fields(request, obj=obj)
return self._fieldsets_only(fieldsets, fields)
def get_fields(self, request, obj=None):
fields = list(super(AccessUserAdmin, self).get_fields(request, obj)) or []
exclude = ['is_staff', 'groups', 'user_permissions']
if not request.user.is_superuser:
exclude = ['is_staff', 'is_superuser', 'groups', 'user_permissions']
if obj and obj.pk != request.user.pk:
exclude = ['is_staff', 'password', 'email', 'groups', 'user_permissions']
return [f for f in fields if not f in exclude]
def save_model(self, request, obj, form, change):
obj.is_staff = True
return super(AccessUserAdmin, self).save_model(request, obj, form, change)
, , , ( is_staff
is_superuser
), , , .
, authorize
. , , .
?, . authorize INSTALLED_APPS
settings.py
, .
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'access',
'trans',
'authorize',
]
python manage.py startapp authorize
models.py
. , .
?, models.py
, , , , INSTALLED_APPS
. , , .
, , ( ) , . , , , .
, , , . , , , , . , , .
AccessManager.register_plugins({
User: CompoundPlugin(
CheckAblePlugin(
appendable=(lambda model, request:
{} if request.user.id and request.user.projects.filter(allow_manage=True) else
False
),
),
ApplyAblePlugin(
visible=(
lambda queryset, request:
queryset if request.user.projects.filter(
allow_manage=True
) else queryset.filter(
projects__project__users__user=request.user.id
).distinct() or queryset.filter(
id=request.user.id
).distinct()
),
changeable=(lambda queryset, request: queryset.filter(id=request.user.id).distinct()),
deleteable=(lambda queryset, request: queryset.filter(id=request.user.id).distinct())
)
),
})
.
-, , , , , . , , , , .
-, , , . , , .
AccessManager.register_plugins({
Project: CompoundPlugin(
CheckAblePlugin(
appendable=(lambda model, request: False),
),
ApplyAblePlugin(
visible=(lambda queryset, request: queryset.filter(
users__user=request.user.id
).distinct()),
changeable=(lambda queryset, request: queryset.filter(
users__user=request.user.id,
users__allow_manage=True
).distinct()),
deleteable=(lambda queryset, request: queryset.none()),
)
),
ProjectMember: CompoundPlugin(
CheckAblePlugin(
appendable=(lambda model, request: {} if request.user.id and request.user.projects.filter(allow_manage=True) else False),
),
ApplyAblePlugin(
visible=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id
).distinct()),
changeable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_manage=True
).distinct()),
deleteable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_manage=True
).distinct()),
)
),
})
, , . , , allow_change
.
, — , , () .
, , :
Driver
— ,Order
— , , , , .
.
AccessManager.register_plugins({
Vehicle: CompoundPlugin(
ApplyAblePlugin(
visible=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id
).distinct()),
changeable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_change=True
).distinct()),
deleteable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_change=True
).distinct()),
)
),
Driver: CompoundPlugin(
ApplyAblePlugin(
visible=(lambda queryset, request: queryset.filter(
Q(project__users__user=request.user.id) | Q(user=request.user)
).distinct()),
changeable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_change=True
).distinct()),
deleteable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_change=True
).distinct()),
)
),
Order: CompoundPlugin(
ApplyAblePlugin(
visible=(lambda queryset, request: queryset.filter(
Q(
project__users__user=request.user.id
) | Q(
client=request.user
) | Q(
driver__user=request.user
)
).distinct()),
changeable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_change=True
).distinct()),
deleteable=(lambda queryset, request: queryset.filter(
project__users__user=request.user.id,
project__users__allow_change=True
).distinct()),
)
),
})
?, .
, Django-Access , , .
- , . , . project
, , .
ProjectMember
:
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "project":
projects = AccessManager(Project).changeable(request)
kwargs["queryset"] = projects
return super(ProjectMemberAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
:
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "project":
projects = Project.objects.filter(users__user=request.user, users__allow_change=True)
kwargs["queryset"] = projects
return super(..., self).formfield_for_foreignkey(db_field, request, **kwargs)
( … ).
. , Django, Django-Access , , Django. access.plugins.DjangoAccessPlugin
, , .
settings.py
. , , , , .
class SuperOnly(AccessPluginBase):
def check_visible(self, model, request):
return request.user.is_superuser and {}
def check_changeable(self, model, request):
return request.user.is_superuser and {}
def check_appendable(self, model, request):
return request.user.is_superuser and {}
def check_deleteable(self, model, request):
return request.user.is_superuser and {}
settings.py
ACCESS_DEFAULT_PLUGIN = "authorize.models.SuperOnly"
, , , .
Mit Hilfe des Django-Access- Pakets haben wir fast alle erforderlichen Regeln formuliert, und die verbleibenden Details der Zugriffsbeschränkung wurden durch kleine Änderungen im Admin-Bereich implementiert.
Wir haben alle Zugriffsbeschränkungsregeln in Form von Abfragen an das DBMS formuliert.
Sie können den vollständigen Quellcode der laufenden Anwendung im GitHub-Repository sehen