Просмотр исходного кода

ACL cachebuster, roles related to ranks.

Rafał Pitoń 11 лет назад
Родитель
Сommit
645e0f75de

+ 17 - 0
misago/acl/cachebuster.py

@@ -0,0 +1,17 @@
+from misago.core import cachebuster as cb
+
+
+ACL_CACHE_NAME = 'misago_acl'
+
+
+def get_version():
+    return cb.get_version(ACL_CACHE_NAME)
+
+
+def is_valid(version):
+    return cb.is_valid(ACL_CACHE_NAME, version)
+
+
+def invalidate():
+    cb.invalidate(ACL_CACHE_NAME)
+

+ 39 - 0
misago/acl/migrations/0002_register_cachebuster.py

@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from misago.core.migrationutils import (with_core_models,
+                                        cachebuster_register_cache)
+
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        cachebuster_register_cache(orm, 'misago_acl')
+
+    def backwards(self, orm):
+        pass
+
+    models = with_core_models('0001_initial', {
+        u'acl.forumrole': {
+            'Meta': {'object_name': 'ForumRole'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'pickled_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        u'acl.role': {
+            'Meta': {'object_name': 'Role'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'pickled_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        }
+    })
+
+    complete_apps = ['acl', 'core']
+
+    symmetrical = True
+
+    depends_on = (
+        ("core", "0001_initial"),
+    )

+ 19 - 10
misago/admin/views/generic.py

@@ -1,5 +1,6 @@
 from django.contrib import messages
 from django.contrib import messages
 from django.core.paginator import Paginator, EmptyPage
 from django.core.paginator import Paginator, EmptyPage
+from django.db import transaction
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from django.views.generic import View
 from django.views.generic import View
@@ -179,7 +180,8 @@ class TargetedView(AdminView):
 
 
     def get_target(self, kwargs):
     def get_target(self, kwargs):
         if len(kwargs) == 1:
         if len(kwargs) == 1:
-            return self.get_model().objects.get(pk=kwargs[kwargs.keys()[0]])
+            select_for_update = self.get_model().objects.select_for_update()
+            return select_for_update.get(pk=kwargs[kwargs.keys()[0]])
         else:
         else:
             return self.get_model()()
             return self.get_model()()
 
 
@@ -190,17 +192,18 @@ class TargetedView(AdminView):
             return None
             return None
 
 
     def dispatch(self, request, *args, **kwargs):
     def dispatch(self, request, *args, **kwargs):
-        target = self.get_target_or_none(request, kwargs)
-        if not target:
-            messages.error(request, self.message_404)
-            return redirect(self.root_link)
+        with transaction.atomic():
+            target = self.get_target_or_none(request, kwargs)
+            if not target:
+                messages.error(request, self.message_404)
+                return redirect(self.root_link)
 
 
-        error = self.check_permissions(request, target)
-        if error:
-            messages.error(request, error)
-            return redirect(self.root_link)
+            error = self.check_permissions(request, target)
+            if error:
+                messages.error(request, error)
+                return redirect(self.root_link)
 
 
-        return self.real_dispatch(request, target)
+            return self.real_dispatch(request, target)
 
 
     def real_dispatch(self, request, target):
     def real_dispatch(self, request, target):
         pass
         pass
@@ -253,6 +256,12 @@ class ModelFormView(FormView):
         else:
         else:
             return FormType(instance=target)
             return FormType(instance=target)
 
 
+    def transaction_pre_save(self, form, request, target):
+        pass
+
+    def transaction_after_save(self, form, request, target):
+        pass
+
     def handle_form(self, form, request, target):
     def handle_form(self, form, request, target):
         form.instance.save()
         form.instance.save()
         if self.message_submit:
         if self.message_submit:

+ 6 - 0
misago/templates/misago/admin/ranks/form.html

@@ -42,6 +42,12 @@
 
 
   </fieldset>
   </fieldset>
   <fieldset>
   <fieldset>
+    <legend>{% trans "Permissions" %}</legend>
+
+    {{ form.roles|as_crispy_field }}
+
+  </fieldset>
+  <fieldset>
     <legend>{% trans "Display and visibility" %}</legend>
     <legend>{% trans "Display and visibility" %}</legend>
 
 
     {{ form.style|as_crispy_field }}
     {{ form.style|as_crispy_field }}

+ 13 - 2
misago/users/forms/admin.py

@@ -1,6 +1,7 @@
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from misago.core import forms
 from misago.core import forms
 from misago.core.validators import validate_sluggable
 from misago.core.validators import validate_sluggable
+from misago.acl.models import Role
 from misago.users.models import Rank
 from misago.users.models import Rank
 
 
 
 
@@ -19,8 +20,12 @@ class RankForm(forms.ModelForm):
         widget=forms.Textarea(attrs={'rows': 3}),
         widget=forms.Textarea(attrs={'rows': 3}),
         help_text=_("Optional description explaining function or status of "
         help_text=_("Optional description explaining function or status of "
                     "members distincted with this rank."))
                     "members distincted with this rank."))
+    roles = forms.ModelMultipleChoiceField(
+        label=_("User roles"), queryset=Role.objects.order_by('name'),
+        required=False,  widget=forms.CheckboxSelectMultiple,
+        help_text=_('Rank can give users with it additional roles.'))
     style = forms.CharField(
     style = forms.CharField(
-        label=_("CSS Class"), required=False,
+        label=_("CSS class"), required=False,
         help_text=_("Optional css class added to content belonging to this "
         help_text=_("Optional css class added to content belonging to this "
                     "rank owner."))
                     "rank owner."))
     is_tab = forms.BooleanField(
     is_tab = forms.BooleanField(
@@ -37,7 +42,13 @@ class RankForm(forms.ModelForm):
     class Meta:
     class Meta:
         model = Rank
         model = Rank
         fields = [
         fields = [
-            'name', 'description', 'style', 'title', 'is_tab', 'is_on_index'
+            'name',
+            'description',
+            'style',
+            'title',
+            'roles',
+            'is_tab',
+            'is_on_index',
         ]
         ]
 
 
     def clean_name(self):
     def clean_name(self):

+ 85 - 0
misago/users/migrations/0004_auto.py

@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding M2M table for field roles on 'Rank'
+        m2m_table_name = db.shorten_name(u'users_rank_roles')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('rank', models.ForeignKey(orm['users.rank'], null=False)),
+            ('role', models.ForeignKey(orm[u'acl.role'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['rank_id', 'role_id'])
+
+
+    def backwards(self, orm):
+        # Removing M2M table for field roles on 'Rank'
+        db.delete_table(db.shorten_name(u'users_rank_roles'))
+
+
+    models = {
+        u'acl.role': {
+            'Meta': {'object_name': 'Role'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'pickled_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'users.rank': {
+            'Meta': {'object_name': 'Rank'},
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_on_index': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_tab': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'roles': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['acl.Role']", 'symmetrical': 'False'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
+        },
+        'users.user': {
+            'Meta': {'object_name': 'User'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'db_index': 'True'}),
+            'email_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'joined_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'rank': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.Rank']", 'on_delete': 'models.PROTECT'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
+            'username_slug': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        }
+    }
+
+    complete_apps = ['users']

+ 9 - 2
misago/users/models/rankmodel.py

@@ -1,6 +1,7 @@
 from django.db import models, transaction
 from django.db import models, transaction
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from misago.admin import site
 from misago.admin import site
+from misago.acl import cachebuster
 from misago.core.utils import slugify
 from misago.core.utils import slugify
 
 
 
 
@@ -19,13 +20,13 @@ class Rank(models.Model):
     name = models.CharField(max_length=255)
     name = models.CharField(max_length=255)
     slug = models.CharField(max_length=255)
     slug = models.CharField(max_length=255)
     description = models.TextField(null=True, blank=True)
     description = models.TextField(null=True, blank=True)
-    style = models.CharField(max_length=255, null=True, blank=True)
     title = models.CharField(max_length=255, null=True, blank=True)
     title = models.CharField(max_length=255, null=True, blank=True)
+    roles = models.ManyToManyField('acl.Role', null=True, blank=True)
+    style = models.CharField(max_length=255, null=True, blank=True)
     is_default = models.BooleanField(default=False)
     is_default = models.BooleanField(default=False)
     is_tab = models.BooleanField(default=False)
     is_tab = models.BooleanField(default=False)
     is_on_index = models.BooleanField(default=False)
     is_on_index = models.BooleanField(default=False)
     order = models.IntegerField(default=0)
     order = models.IntegerField(default=0)
-    #roles = models.ManyToManyField('Role')
 
 
     objects = RankManager()
     objects = RankManager()
 
 
@@ -39,8 +40,14 @@ class Rank(models.Model):
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
         if not self.pk:
         if not self.pk:
             self.set_order()
             self.set_order()
+        else:
+            cachebuster.invalidate()
         return super(Rank, self).save(*args, **kwargs)
         return super(Rank, self).save(*args, **kwargs)
 
 
+    def delete(self, *args, **kwargs):
+        cachebuster.invalidate()
+        return super(Rank, self).delete(*args, **kwargs)
+
     def set_name(self, name):
     def set_name(self, name):
         self.name = name
         self.name = name
         self.slug = slugify(name)
         self.slug = slugify(name)

+ 9 - 0
misago/users/views/rankadmin.py

@@ -12,6 +12,15 @@ class RankAdmin(generic.AdminBaseMixin):
     templates_dir = 'misago/admin/ranks'
     templates_dir = 'misago/admin/ranks'
     message_404 = _("Requested rank does not exist.")
     message_404 = _("Requested rank does not exist.")
 
 
+    def update_roles(self, target, roles):
+        target.roles.clear()
+        if roles:
+            target.roles.add(*roles)
+
+    def handle_form(self, form, request, target):
+        super(RankAdmin, self).handle_form(form, request, target)
+        self.update_roles(target, form.cleaned_data['roles'])
+
 
 
 class RanksList(RankAdmin, generic.ListView):
 class RanksList(RankAdmin, generic.ListView):
     ordering = (('order', None),)
     ordering = (('order', None),)