ฉันจะใช้สิทธิ์ Django โดยไม่กำหนดประเภทเนื้อหาหรือโมเดลได้อย่างไร


86

ฉันต้องการใช้ระบบที่ใช้สิทธิ์เพื่อ จำกัด การกระทำบางอย่างในแอปพลิเคชัน Django ของฉัน การดำเนินการเหล่านี้ไม่จำเป็นต้องเกี่ยวข้องกับรุ่นใดรุ่นหนึ่ง (เช่นการเข้าถึงส่วนต่างๆในแอปพลิเคชันการค้นหา ... ) ดังนั้นฉันจึงไม่สามารถใช้กรอบการอนุญาตหุ้นได้โดยตรงเนื่องจากPermissionโมเดลต้องการการอ้างอิงถึงประเภทเนื้อหาที่ติดตั้ง

ฉันสามารถเขียนรูปแบบการอนุญาตของตัวเองได้ แต่จากนั้นฉันต้องเขียนสิ่งต่างๆทั้งหมดที่รวมอยู่ในการอนุญาต Django เช่น:

  • ความเป็นไปได้ในการกำหนดสิทธิ์ให้กับผู้ใช้และกลุ่ม
  • มัณฑนากรpermission_required
  • User.has_perm และวิธีการของผู้ใช้ที่เกี่ยวข้อง
  • ตัวแปรแม่แบบperms
  • ...

ฉันได้ตรวจสอบแอพบางตัวเช่นdjango-authorityและdjango-Guardianแต่ดูเหมือนว่าจะให้สิทธิ์มากยิ่งขึ้นควบคู่ไปกับระบบโมเดลโดยการอนุญาตการอนุญาตต่ออ็อบเจ็กต์

มีวิธีการนำกรอบงานนี้กลับมาใช้ใหม่โดยไม่ต้องกำหนดโมเดลใด ๆ (นอกเหนือจากUserและGroup) สำหรับโครงการหรือไม่

คำตอบ:


57

Django ของPermissionรูปแบบต้องใช้ContentTypeอินสแตนซ์

ฉันคิดว่าวิธีหนึ่งคือการสร้างดัมมี่ContentTypeที่ไม่เกี่ยวข้องกับโมเดลใด ๆ ( ฟิลด์app_labelและmodelสามารถตั้งค่าเป็นค่าสตริงใดก็ได้)

หากคุณต้องการให้ทุกอย่างสะอาดและสวยงามคุณสามารถสร้างPermission โมเดลพร็อกซีที่จัดการรายละเอียดที่น่าเกลียดทั้งหมดของหุ่นจำลองContentTypeและสร้างอินสแตนซ์สิทธิ์ คุณยังสามารถเพิ่มตัวจัดการแบบกำหนดเองที่กรองPermissionอินสแตนซ์ทั้งหมดที่เกี่ยวข้องกับโมเดลจริงได้


3
หากคุณไม่ทราบฉันจะตอบคำถามของคุณพร้อมกับการใช้งานของฉัน
Chewie

น่าเศร้าที่ฉันไม่สามารถอนุมัติได้เนื่องจากฉันไม่มีชื่อเสียงมากพอที่จะตรวจสอบการแก้ไขของคุณ (ขอให้ฉัน + 2k) ผู้ใช้รายอื่นปฏิเสธการแก้ไขของคุณดังนั้นฉันขอแนะนำให้คุณเพิ่มเป็นคำตอบอื่น (คุณมีคะแนนโหวตเพิ่ม!) ขอขอบคุณอีกครั้ง
Gonzalo

1
แปลก. คำตอบของคุณเสร็จสมบูรณ์แล้วดังนั้นจึงเป็นเรื่องที่สมเหตุสมผลที่จะทำการแก้ไข อย่างไรก็ตามฉันใส่ไว้ในคำตอบอื่น
Chewie

149

สำหรับพวกคุณที่ยังคงค้นหา:

คุณสามารถสร้างแบบจำลองเสริมโดยไม่มีตารางฐานข้อมูล โมเดลนั้นสามารถให้สิทธิ์ในโปรเจ็กต์ของคุณได้ตามต้องการ ไม่จำเป็นต้องจัดการกับ ContentType หรือสร้างออบเจ็กต์สิทธิ์อย่างชัดเจน

from django.db import models
        
class RightsSupport(models.Model):
            
    class Meta:
        
        managed = False  # No database table creation or deletion  \
                         # operations will be performed for this model. 
                
        default_permissions = () # disable "add", "change", "delete"
                                 # and "view" default permissions

        permissions = ( 
            ('customer_rights', 'Global customer rights'),  
            ('vendor_rights', 'Global vendor rights'), 
            ('any_rights', 'Global any rights'), 
        )

หลังจากนั้นmanage.py makemigrationsและmanage.py migrateคุณสามารถใช้สิทธิ์เหล่านี้ได้เหมือนที่อื่น ๆ

# Decorator

@permission_required('app.customer_rights')
def my_search_view(request):
    …

# Inside a view

def my_search_view(request):
    request.user.has_perm('app.customer_rights')

# In a template
# The currently logged-in user’s permissions are stored in the template variable {{ perms }}

{% if perms.app.customer_rights %}
    <p>You can do any customer stuff</p>
{% endif %}

2
นั่นคืออัจฉริยะช่วยวันของฉัน!
Reorx

2
ไม่มีอะไรเปลี่ยนแปลงหลังจากที่ฉันเรียกใช้ Manage.py migrate ... ฉันไม่เห็นสิทธิ์ใหม่ ๆ :(
Agey

2
คุณได้เพิ่มแอปลงในโครงการของคุณ (INSTALLED_APPS) หรือไม่
Dmitry

2
คำตอบนี้เหมาะมาก ฉันยัง [] ed default_permissions เพิ่ม NotImplementedError บนการบันทึก () ของโมเดลและอาจพิจารณาทำให้มี _ * _ permission () ส่งคืน False หากโมเดลที่ไม่มีการจัดการนั้นเป็นเพียงแค่สิทธิ์นี้เท่านั้น
Douglas Denhartog

4
ฉันขอแนะนำให้เพิ่มคลาส Meta ดังต่อไปนี้: default_permissions = (). สิ่งนี้จะป้องกันไม่ให้ Django สร้างสิทธิ์เพิ่ม / เปลี่ยนแปลง / ลบ / ดูเริ่มต้นสำหรับรุ่นนี้โดยอัตโนมัติซึ่งอาจไม่จำเป็นหากคุณใช้วิธีนี้
จอร์แดน

51

ต่อไปนี้คำแนะนำของกอนซาโล่ผมใช้รุ่นพร็อกซี่และผู้จัดการที่กำหนดเองที่จะจัดการกับ "modelless" ฉันสิทธิ์กับชนิดเนื้อหาของหุ่น

from django.db import models
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


class GlobalPermissionManager(models.Manager):
    def get_query_set(self):
        return super(GlobalPermissionManager, self).\
            get_query_set().filter(content_type__name='global_permission')


class GlobalPermission(Permission):
    """A global permission, not attached to a model"""

    objects = GlobalPermissionManager()

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        ct, created = ContentType.objects.get_or_create(
            name="global_permission", app_label=self._meta.app_label
        )
        self.content_type = ct
        super(GlobalPermission, self).save(*args, **kwargs)

10
ขอบคุณสำหรับรหัสมันจะเป็นการดีที่จะแสดงตัวอย่างวิธีการใช้รหัสนี้
Ken Cochrane

2
การอนุญาตแบบจำลองนั้นควรอยู่ที่ไหน
Mirat Can Bayrak

4
ในการสร้าง GlobalPermission: จาก app.models import GlobalPermission gp = GlobalPermission.objects.create (codename = 'can_do_it', name = 'Can do it') เมื่อเรียกใช้แล้วคุณสามารถเพิ่มสิทธิ์นั้นให้กับผู้ใช้ / กลุ่มได้เหมือนกับการอนุญาตอื่น ๆ .
Julien Grenier

3
@JulienGrenier รหัสแบ่งใน Django FieldError: Cannot resolve keyword 'name' into field. Choices are: app_label, id, logentry, model, permission1.8:
maciek

2
คำเตือน: Django เวอร์ชันใหม่กว่า (อย่างน้อย 1.10) จำเป็นต้องแทนที่เมธอด "get_queryset" (สังเกตการขาด _ ระหว่างคำว่า "query" และ "set)
Lobe

10

แก้ไขคำตอบของ Chewie ใน Django 1.8 ซึ่งตามที่ร้องขอในความคิดเห็นเล็กน้อย

มีระบุไว้ในบันทึกประจำรุ่น:

ฟิลด์ชื่อของ django.contrib.contenttypes.models.ContentType ถูกลบออกโดยการโอนย้ายและแทนที่ด้วยคุณสมบัติ นั่นหมายความว่าจะไม่สามารถสอบถามหรือกรอง ContentType ตามช่องนี้ได้อีกต่อไป

ดังนั้นจึงเป็น 'ชื่อ' ในการอ้างอิงใน ContentType ที่ใช้ไม่ได้ใน GlobalPermissions

เมื่อแก้ไขแล้วฉันจะได้รับสิ่งต่อไปนี้:

from django.db import models
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


class GlobalPermissionManager(models.Manager):
    def get_queryset(self):
        return super(GlobalPermissionManager, self).\
            get_queryset().filter(content_type__model='global_permission')


class GlobalPermission(Permission):
    """A global permission, not attached to a model"""

    objects = GlobalPermissionManager()

    class Meta:
        proxy = True
        verbose_name = "global_permission"

    def save(self, *args, **kwargs):
        ct, created = ContentType.objects.get_or_create(
            model=self._meta.verbose_name, app_label=self._meta.app_label,
        )
        self.content_type = ct
        super(GlobalPermission, self).save(*args)

คลาส GlobalPermissionManager ไม่มีการเปลี่ยนแปลง แต่รวมไว้เพื่อความสมบูรณ์


1
สิ่งนี้ยังไม่สามารถแก้ไขได้สำหรับ django 1.8 ในขณะที่ syncdb django ยืนยันว่าฟิลด์ "name" ไม่สามารถเป็นค่าว่างได้
Armita

มันใช้ได้ผลสำหรับฉัน แต่ฉันไม่ได้ใช้การย้ายข้อมูลเนื่องจากสิ่งที่ไม่ใช่ django ดั้งเดิมยังคงอยู่ในโครงการของฉัน คุณกำลังอัปเกรดจาก django ก่อนหน้าหรือไม่เพราะไม่ควรเป็นฟิลด์ชื่อใน 1.8
rgammans

4

นี่คือทางเลือกอื่น ก่อนอื่นให้ถามตัวเอง: ทำไมไม่สร้าง Dummy-Model ที่มีอยู่จริงใน DB แต่ไม่เคยถูกใช้เลยยกเว้นการระงับการอนุญาต นั่นไม่ดี แต่ฉันคิดว่ามันถูกต้องและเป็นวิธีแก้ปัญหาที่ตรงไปตรงมา

from django.db import models

class Permissions(models.Model):

    can_search_blue_flower = 'my_app.can_search_blue_flower'

    class Meta:
        permissions = [
            ('can_search_blue_flower', 'Allowed to search for the blue flower'),
        ]

วิธีแก้ปัญหาข้างต้นมีข้อดีคือคุณสามารถใช้ตัวแปรPermissions.can_search_blue_flowerในซอร์สโค้ดของคุณแทนที่จะใช้สตริงตัวอักษร "my_app.can_search_blue_flower" ซึ่งหมายความว่าพิมพ์ผิดน้อยลงและมีการเติมข้อความอัตโนมัติใน IDE มากขึ้น


1
การใช้managed=Falseไม่อนุญาตให้คุณใช้Permissions.can_search_blue_flowerด้วยเหตุผลบางประการหรือไม่?
Sam Bobel

@SamBobel ใช่คุณพูดถูก ฉันเดาว่าฉันเพิ่งลอง "นามธรรม" ครั้งสุดท้าย
guettli

1

คุณสามารถใช้สิ่งproxy modelนี้กับประเภทเนื้อหาจำลอง

from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


class CustomPermission(Permission):

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        ct, created = ContentType.objects.get_or_create(
            model=self._meta.verbose_name, app_label=self._meta.app_label,
        )
        self.content_type = ct
        super(CustomPermission, self).save(*args)

ตอนนี้คุณสามารถสร้างได้รับอนุญาตที่มีเพียงnameและcodenameการได้รับอนุญาตจากที่CustomPermissionรุ่น

 CustomPermission.objects.create(name='Can do something', codename='can_do_something')

และคุณสามารถสอบถามและแสดงเฉพาะสิทธิ์ที่กำหนดเองในเทมเพลตของคุณเช่นนี้

 CustomPermission.objects.filter(content_type__model='custom permission')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.