Some refactoring and misc improvements

This commit is contained in:
Skia 2016-02-05 16:59:42 +01:00
parent ed080b76a2
commit a14d940db2
16 changed files with 199 additions and 74 deletions

View File

@ -19,9 +19,33 @@ There is a Doxyfile at the root of the project, meaning that if you have Doxygen
generate a complete HTML documentation that will be available in the *./doc/html/* folder. generate a complete HTML documentation that will be available in the *./doc/html/* folder.
### Dependencies: ### Dependencies:
* Django 1.8 See requirements.txt
* Pillow
The development is done with sqlite, but it is advised to set a more robust DBMS for production (Postgresql for example) The development is done with sqlite, but it is advised to set a more robust DBMS for production (Postgresql for example)
### Misc about development
#### Controlling the rights
When you need to protect an object, there are three levels:
* Editing the object properties
* Editing the object various values
* Viewing the object
Now you have many solutions in your model:
* You can define a `is_owned_by(self, user)`, a `can_be_edited_by(self, user)`, and/or a `can_be_viewed_by(self, user)`
method, each returning True is the user passed can edit/view the object, False otherwise.
This allows you to make complex request when the group solution is not powerful enough.
It's useful too when you want to define class-wide permissions, e.g. the club members, that are viewable only for
Subscribers.
* You can add an `owner_group` field, as a ForeignKey to Group. Second is an `edit_groups` field, as a ManyToMany to
Group, and third is a `view_groups`, same as for edit.
Finally, when building a class based view, which is highly advised, you just have to inherit it from CanEditPropMixin,
CanEditMixin, or CanViewMixin, which are located in core.views. Your view will then be protected using either the
appropriate group fields, or the right method to check user permissions.

View File

@ -14,12 +14,12 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='club', model_name='club',
name='edit_group', name='edit_groups',
field=models.ManyToManyField(to='core.Group', blank=True, related_name='editable_club'), field=models.ManyToManyField(to='core.Group', blank=True, related_name='editable_club'),
), ),
migrations.AddField( migrations.AddField(
model_name='club', model_name='club',
name='view_group', name='view_groups',
field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_club'), field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_club'),
), ),
] ]

View File

@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from core.models import User, Group from core.models import User, Group
from subscription.models import Subscriber
# Create your models here. # Create your models here.
@ -31,8 +32,8 @@ class Club(models.Model):
# email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically # email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically
owner_group = models.ForeignKey(Group, related_name="owned_club", owner_group = models.ForeignKey(Group, related_name="owned_club",
default=settings.AE_GROUPS['root']['id']) default=settings.AE_GROUPS['root']['id'])
edit_group = models.ManyToManyField(Group, related_name="editable_club", blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True)
view_group = models.ManyToManyField(Group, related_name="viewable_club", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True)
def check_loop(self): def check_loop(self):
"""Raise a validation error when a loop is found within the parent list""" """Raise a validation error when a loop is found within the parent list"""
@ -53,6 +54,38 @@ class Club(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('club:club_view', kwargs={'club_id': self.id}) return reverse('club:club_view', kwargs={'club_id': self.id})
def is_owned_by(self, user):
"""
Method to see if that object can be super edited by the given user
"""
if user.groups.filter(name=settings.AE_GROUPS['board']['name']).exists():
return True
return False
def can_be_edited_by(self, user):
"""
Method to see if that object can be edited by the given user
"""
ms = self.get_membership_for(user)
if ms is not None and ms.role >= 7:
return True
return False
def can_be_viewed_by(self, user):
"""
Method to see if that object can be seen by the given user
"""
sub = Subscriber.objects.filter(pk=user.pk).first()
if sub is None:
return False
return sub.is_subscribed()
def get_membership_for(self, user):
"""
Returns the current membership the given user
"""
return self.members.filter(user=user.id).filter(end_date=None).first()
class Membership(models.Model): class Membership(models.Model):
""" """
The Membership class makes the connection between User and Clubs The Membership class makes the connection between User and Clubs

View File

@ -3,17 +3,16 @@
{% block content %} {% block content %}
<h3>Club</h3> <h3>Club</h3>
<p><a href="{{ url('club:club_list') }}">Back to list</a></p> <p><a href="{{ url('club:club_list') }}">Back to list</a></p>
{% if user.can_edit(club) %} {% if can_edit(club, user) %}
<p><a href="{{ url('club:club_edit', club_id=club.pk) }}">Edit</a></p> <p><a href="{{ url('club:club_edit', club_id=club.pk) }}">Edit</a></p>
<p><a href="{{ url('club:club_members', club_id=club.pk) }}">Edit members</a></p>
{% endif %} {% endif %}
{% if user.is_owner(club) or user.membership.filter(end_date=None).filter(club=club.id).first() is not none %} {% if can_edit_prop(club, user) %}
<p><a href="{{ url('club:club_prop', club_id=club.pk) }}">Prop</a> <p><a href="{{ url('club:club_prop', club_id=club.pk) }}">Prop</a>
TODO FIXME: this code sucks and is just a test!
We need something really easy to manage the views rights regarding the subscribing status and the membership of
someone!
</p> </p>
{% endif %} {% endif %}
{% if can_view(club, user) %}
<p><a href="{{ url('club:club_members', club_id=club.pk) }}">Members</a></p>
{% endif %}
<h3>{{ club.name }}</h3> <h3>{{ club.name }}</h3>
<p>{{ club.address }}</p> <p>{{ club.address }}</p>
<ul> <ul>

View File

@ -6,7 +6,7 @@ urlpatterns = [
url(r'^$', ClubListView.as_view(), name='club_list'), url(r'^$', ClubListView.as_view(), name='club_list'),
url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'), url(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'),
url(r'^(?P<club_id>[0-9]+)/edit$', ClubEditView.as_view(), name='club_edit'), url(r'^(?P<club_id>[0-9]+)/edit$', ClubEditView.as_view(), name='club_edit'),
url(r'^(?P<club_id>[0-9]+)/members$', ClubEditMembersView.as_view(), name='club_members'), url(r'^(?P<club_id>[0-9]+)/members$', ClubMembersView.as_view(), name='club_members'),
url(r'^(?P<club_id>[0-9]+)/prop$', ClubEditPropView.as_view(), name='club_prop'), url(r'^(?P<club_id>[0-9]+)/prop$', ClubEditPropView.as_view(), name='club_prop'),
#url(r'^(?P<club_id>[0-9]+)/tools$', ClubToolsView.as_view(), name='club_tools'), #url(r'^(?P<club_id>[0-9]+)/tools$', ClubToolsView.as_view(), name='club_tools'),

View File

@ -3,10 +3,12 @@ from django.shortcuts import render
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView from django.views.generic.edit import UpdateView, CreateView
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.core.exceptions import ValidationError
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin
from club.models import Club, Membership from club.models import Club, Membership
from subscription.views import SubscriberMixin
class ClubListView(CanViewMixin, ListView): class ClubListView(CanViewMixin, ListView):
model = Club model = Club
@ -31,23 +33,31 @@ class ClubMemberForm(forms.ModelForm):
fields = ['user', 'role'] fields = ['user', 'role']
def clean(self): def clean(self):
print(self.__dict__) ret = super(ClubMemberForm, self).clean()
# TODO: see how to get access to request.user! We need some right validation somewhere! ms = self.instance.club.get_membership_for(self._user)
return super(ClubMemberForm, self).clean() if ms is not None and ms.role >= self.cleaned_data['role']:
return ret
raise ValidationError("You do not have the permission to do that")
class ClubEditMembersView(CanEditMixin, UpdateView): class ClubMembersView(CanViewMixin, UpdateView):
model = Club model = Club
pk_url_kwarg = "club_id" pk_url_kwarg = "club_id"
form_class = ClubMemberForm form_class = ClubMemberForm
template_name = 'club/club_members.jinja' template_name = 'club/club_members.jinja'
def __init__(self, *args, **kwargs):
super(ClubMembersView, self).__init__(*args, **kwargs)
# TODO FIXME: error forbidden when adding new member to club, because self.object changes to the Membership object
# somewhere!!!
def get_form(self): def get_form(self):
form = super(ClubEditMembersView, self).get_form() form = super(ClubMembersView, self).get_form()
if 'user' in form.data and form.data.get('user') != '': # Load an existing membership if possible if 'user' in form.data and form.data.get('user') != '': # Load an existing membership if possible
form.instance = Membership.objects.filter(club=self.object).filter(user=form.data.get('user')).filter(end_date=None).first() form.instance = Membership.objects.filter(club=self.object).filter(user=form.data.get('user')).filter(end_date=None).first()
if form.instance is None: # Instanciate a new membership if form.instance is None: # Instanciate a new membership
form.instance = Membership(club=self.object, user=self.request.user) form.instance = Membership(club=self.object, user=self.request.user)
form.initial = {'user': self.request.user} form.initial = {'user': self.request.user}
form._user = self.request.user
return form return form
class ClubEditPropView(CanEditPropMixin, UpdateView): class ClubEditPropView(CanEditPropMixin, UpdateView):

View File

@ -1 +1 @@
[{"pk": 1, "model": "core.page", "fields": {"full_name": "guy2", "owner_group": 1, "parent": null, "edit_group": [], "name": "guy2", "view_group": []}}, {"pk": 2, "model": "core.page", "fields": {"full_name": "guy2/bibou", "owner_group": 1, "parent": 1, "edit_group": [], "name": "bibou", "view_group": []}}, {"pk": 3, "model": "core.page", "fields": {"full_name": "guy2/bibou/troll", "owner_group": 1, "parent": 2, "edit_group": [], "name": "troll", "view_group": []}}, {"pk": 4, "model": "core.page", "fields": {"full_name": "guy", "owner_group": 1, "parent": null, "edit_group": [1], "name": "guy", "view_group": [1]}}, {"pk": 5, "model": "core.page", "fields": {"full_name": "bibou", "owner_group": 3, "parent": null, "edit_group": [1], "name": "bibou", "view_group": []}}, {"pk": 6, "model": "core.page", "fields": {"full_name": "guy2/guy", "owner_group": 1, "parent": 1, "edit_group": [], "name": "guy", "view_group": []}}] [{"pk": 1, "model": "core.page", "fields": {"full_name": "guy2", "owner_group": 1, "parent": null, "edit_groups": [], "name": "guy2", "view_groups": []}}, {"pk": 2, "model": "core.page", "fields": {"full_name": "guy2/bibou", "owner_group": 1, "parent": 1, "edit_group": [], "name": "bibou", "view_group": []}}, {"pk": 3, "model": "core.page", "fields": {"full_name": "guy2/bibou/troll", "owner_group": 1, "parent": 2, "edit_group": [], "name": "troll", "view_group": []}}, {"pk": 4, "model": "core.page", "fields": {"full_name": "guy", "owner_group": 1, "parent": null, "edit_group": [1], "name": "guy", "view_group": [1]}}, {"pk": 5, "model": "core.page", "fields": {"full_name": "bibou", "owner_group": 3, "parent": null, "edit_group": [1], "name": "bibou", "view_group": []}}, {"pk": 6, "model": "core.page", "fields": {"full_name": "guy2/guy", "owner_group": 1, "parent": 1, "edit_group": [], "name": "guy", "view_group": []}}]

View File

@ -16,9 +16,10 @@ class Command(BaseCommand):
parser.add_argument('--prod', action="store_true") parser.add_argument('--prod', action="store_true")
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
try: try:
os.unlink(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 'db.sqlite3')) os.unlink(os.path.join(root_path, 'db.sqlite3'))
os.mkdir(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))+'/data') os.mkdir(os.path.join(root_path)+'/data')
except Exception as e: except Exception as e:
print(e) print(e)
call_command('migrate') call_command('migrate')
@ -37,37 +38,41 @@ class Command(BaseCommand):
# Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment # Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment
if not options['prod']: if not options['prod']:
print("Dev mode, adding some test data") print("Dev mode, adding some test data")
# Adding user Skia
s = User(username='skia', last_name="Kia", first_name="S'", s = User(username='skia', last_name="Kia", first_name="S'",
email="skia@git.an", email="skia@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12")
is_superuser=True, is_staff=True)
s.set_password("plop") s.set_password("plop")
s.save() s.save()
# Adding user Guy
u = User(username='guy', last_name="Carlier", first_name="Guy", u = User(username='guy', last_name="Carlier", first_name="Guy",
email="guy@git.an", email="guy@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
u.set_password("plop") u.set_password("plop")
u.save() u.save()
# Adding syntax help page
p = Page(name='Aide_sur_la_syntaxe') p = Page(name='Aide_sur_la_syntaxe')
p.set_lock(s) p.set_lock(s)
p.save() p.save()
PageRev(page=p, title="Aide sur la syntaxe", author=s, content=""" PageRev(page=p, title="Aide sur la syntaxe", author=s, content="""
Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site. Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site.
""").save() """).save()
# Adding README
# Accounting test values: p = Page(name='README')
Customer(user=s, account_id="6568j").save() p.set_lock(s)
p = ProductType(name="Bières bouteilles")
p.save() p.save()
Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7", p.view_groups=[settings.AE_GROUPS['public']['id']]
special_selling_price="1.6").save() p.set_lock(s)
GeneralJournal(start_date="2015-06-12", name="A15").save() p.save()
with open(os.path.join(root_path)+'/README.md', 'r') as rm:
PageRev(page=p, title="REAMDE", author=s, content=rm.read()).save()
# Subscription # Subscription
Subscription(member=Subscriber.objects.filter(pk=s.pk).first(), subscription_type=list(settings.AE_SUBSCRIPTIONS.keys())[0], Subscription(member=Subscriber.objects.filter(pk=s.pk).first(), subscription_type=list(settings.AE_SUBSCRIPTIONS.keys())[0],
payment_method=settings.AE_PAYMENT_METHOD[0]).save() payment_method=settings.AE_PAYMENT_METHOD[0]).save()
# Clubs
Club(name="Bibo'UT", unix_name="bibout", Club(name="Bibo'UT", unix_name="bibout",
address="46 de la Boustifaille", parent=ae).save() address="46 de la Boustifaille", parent=ae).save()
guyut = Club(name="Guy'UT", unix_name="guyut", guyut = Club(name="Guy'UT", unix_name="guyut",
@ -77,3 +82,12 @@ Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site.
address="Woenzel", parent=guyut).save() address="Woenzel", parent=guyut).save()
Club(name="BdF", unix_name="bdf", Club(name="BdF", unix_name="bdf",
address="Guyéuéyuéyuyé").save() address="Guyéuéyuéyuyé").save()
# Accounting test values:
Customer(user=s, account_id="6568j").save()
p = ProductType(name="Bières bouteilles")
p.save()
Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6").save()
GeneralJournal(start_date="2015-06-12", name="A15").save()

View File

@ -54,10 +54,10 @@ class Migration(migrations.Migration):
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('name', models.CharField(max_length=30, verbose_name='page name')), ('name', models.CharField(max_length=30, verbose_name='page name')),
('_full_name', models.CharField(max_length=255, verbose_name='page name', blank=True)), ('_full_name', models.CharField(max_length=255, verbose_name='page name', blank=True)),
('edit_group', models.ManyToManyField(to='core.Group', related_name='editable_page', blank=True)), ('edit_groups', models.ManyToManyField(to='core.Group', related_name='editable_page', blank=True)),
('owner_group', models.ForeignKey(to='core.Group', related_name='owned_page', default=1)), ('owner_group', models.ForeignKey(to='core.Group', related_name='owned_page', default=1)),
('parent', models.ForeignKey(to='core.Page', on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children', blank=True)), ('parent', models.ForeignKey(to='core.Page', on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children', blank=True)),
('view_group', models.ManyToManyField(to='core.Group', related_name='viewable_page', blank=True)), ('view_groups', models.ManyToManyField(to='core.Group', related_name='viewable_page', blank=True)),
], ],
options={ options={
'permissions': (('change_prop_page', "Can change the page's properties (groups, ...)"), ('view_page', 'Can view the page')), 'permissions': (('change_prop_page', "Can change the page's properties (groups, ...)"), ('view_page', 'Can view the page')),
@ -79,7 +79,7 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name='user',
name='edit_group', name='edit_groups',
field=models.ManyToManyField(to='core.Group', related_name='editable_user', blank=True), field=models.ManyToManyField(to='core.Group', related_name='editable_user', blank=True),
), ),
migrations.AddField( migrations.AddField(
@ -99,7 +99,7 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name='user',
name='view_group', name='view_groups',
field=models.ManyToManyField(to='core.Group', related_name='viewable_user', blank=True), field=models.ManyToManyField(to='core.Group', related_name='viewable_user', blank=True),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(

View File

@ -65,8 +65,8 @@ class User(AbstractBaseUser, PermissionsMixin):
date_joined = models.DateField(_('date joined'), auto_now_add=True) date_joined = models.DateField(_('date joined'), auto_now_add=True)
owner_group = models.ForeignKey(Group, related_name="owned_user", owner_group = models.ForeignKey(Group, related_name="owned_user",
default=settings.AE_GROUPS['root']['id']) default=settings.AE_GROUPS['root']['id'])
edit_group = models.ManyToManyField(Group, related_name="editable_user", blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_user", blank=True)
view_group = models.ManyToManyField(Group, related_name="viewable_user", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_user", blank=True)
objects = UserManager() objects = UserManager()
@ -159,6 +159,8 @@ class User(AbstractBaseUser, PermissionsMixin):
self.has_perm(obj.__class__.__module__.split('.')[0]+".change_prop_"+obj.__class__.__name__.lower()) or self.has_perm(obj.__class__.__module__.split('.')[0]+".change_prop_"+obj.__class__.__name__.lower()) or
self.groups.filter(id=settings.AE_GROUPS['root']['id']).exists()): self.groups.filter(id=settings.AE_GROUPS['root']['id']).exists()):
return True return True
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
return True
return False return False
def can_edit(self, obj): def can_edit(self, obj):
@ -167,12 +169,14 @@ class User(AbstractBaseUser, PermissionsMixin):
""" """
if self.is_owner(obj): if self.is_owner(obj):
return True return True
if hasattr(obj, "edit_group"): if hasattr(obj, "edit_groups"):
for g in obj.edit_group.all(): for g in obj.edit_groups.all():
if self.groups.filter(name=g.name).exists(): if self.groups.filter(name=g.name).exists():
return True return True
if isinstance(obj, User) and obj == self: if isinstance(obj, User) and obj == self:
return True return True
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
return True
if self.has_perm(obj.__class__.__module__.split('.')[0]+".change_"+obj.__class__.__name__.lower()): if self.has_perm(obj.__class__.__module__.split('.')[0]+".change_"+obj.__class__.__name__.lower()):
return True return True
return False return False
@ -183,10 +187,12 @@ class User(AbstractBaseUser, PermissionsMixin):
""" """
if self.can_edit(obj): if self.can_edit(obj):
return True return True
if hasattr(obj, "view_group"): if hasattr(obj, "view_groups"):
for g in obj.view_group.all(): for g in obj.view_groups.all():
if self.groups.filter(name=g.name).exists(): if self.groups.filter(name=g.name).exists():
return True return True
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
return True
if self.has_perm(obj.__class__.__module__.split('.')[0]+".view_"+obj.__class__.__name__.lower()): if self.has_perm(obj.__class__.__module__.split('.')[0]+".view_"+obj.__class__.__name__.lower()):
return True return True
return False return False
@ -202,7 +208,7 @@ class AnonymousUser(AuthAnonymousUser):
return False return False
def can_view(self, obj): def can_view(self, obj):
if obj.view_group.filter(pk=settings.AE_GROUPS['public']['id']).exists(): if obj.view_groups.filter(pk=settings.AE_GROUPS['public']['id']).exists():
return True return True
return False return False
@ -236,8 +242,8 @@ class Page(models.Model):
_full_name = models.CharField(_('page name'), max_length=255, blank=True) _full_name = models.CharField(_('page name'), max_length=255, blank=True)
owner_group = models.ForeignKey(Group, related_name="owned_page", owner_group = models.ForeignKey(Group, related_name="owned_page",
default=settings.AE_GROUPS['root']['id']) default=settings.AE_GROUPS['root']['id'])
edit_group = models.ManyToManyField(Group, related_name="editable_page", blank=True) edit_groups = models.ManyToManyField(Group, related_name="editable_page", blank=True)
view_group = models.ManyToManyField(Group, related_name="viewable_page", blank=True) view_groups = models.ManyToManyField(Group, related_name="viewable_page", blank=True)
lock_mutex = {} lock_mutex = {}
@ -397,10 +403,10 @@ class PageRev(models.Model):
def __getattribute__(self, attr): def __getattribute__(self, attr):
if attr == "owner_group": if attr == "owner_group":
return self.page.owner_group return self.page.owner_group
elif attr == "edit_group": elif attr == "edit_groups":
return self.page.edit_group return self.page.edit_groups
elif attr == "view_group": elif attr == "view_groups":
return self.page.view_group return self.page.view_groups
elif attr == "unset_lock": elif attr == "unset_lock":
return self.page.unset_lock return self.page.unset_lock
else: else:

View File

@ -12,6 +12,20 @@ def forbidden(request):
def not_found(request): def not_found(request):
return render(request, "core/404.jinja") return render(request, "core/404.jinja")
def can_edit_prop(obj, user):
if obj is None or user.is_owner(obj):
return True
return False
def can_edit(obj, user):
if obj is None or user.can_edit(obj):
return True
return can_edit_prop(obj, user)
def can_view(obj, user):
if obj is None or user.can_view(obj):
return True
return can_edit(obj, user)
class CanEditPropMixin(View): class CanEditPropMixin(View):
""" """
@ -22,8 +36,11 @@ class CanEditPropMixin(View):
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
res = super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) res = super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs)
if ((hasattr(self, 'object') and (self.object is None or self.request.user.is_owner(self.object))) or if hasattr(self, 'object'):
(hasattr(self, 'object_list') and (self.object_list is None or self.object_list is [] or self.request.user.is_owner(self.object_list[0])))): obj = self.object
elif hasattr(self, 'object_list'):
obj = self.object_list[0] if self.object_list else None
if can_edit_prop(obj, self.request.user):
return res return res
try: # Always unlock when 403 try: # Always unlock when 403
self.object.unset_lock() self.object.unset_lock()
@ -32,35 +49,38 @@ class CanEditPropMixin(View):
class CanEditMixin(View): class CanEditMixin(View):
""" """
This view makes exactly the same this as its direct parent, but checks the group on the edit_group field of the This view makes exactly the same this as its direct parent, but checks the group on the edit_groups field of the
object object
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
# TODO: WIP: fix permissions with exceptions!
res = super(CanEditMixin, self).dispatch(request, *arg, **kwargs) res = super(CanEditMixin, self).dispatch(request, *arg, **kwargs)
if ((hasattr(self, 'object') and (self.object is None or self.request.user.can_edit(self.object))) or if hasattr(self, 'object'):
(hasattr(self, 'object_list') and (self.object_list is None or self.object_list is [] or self.request.user.can_edit(self.object_list[0])))): obj = self.object
elif hasattr(self, 'object_list'):
obj = self.object_list[0] if self.object_list else None
if can_edit(obj, self.request.user):
return res return res
try: # Always unlock when 403 try: # Always unlock when 403
self.object.unset_lock() self.object.unset_lock()
except: pass except: pass
print("CanEditMixin 403")
raise PermissionDenied raise PermissionDenied
class CanViewMixin(View): class CanViewMixin(View):
""" """
This view still makes exactly the same this as its direct parent, but checks the group on the view_group field of This view still makes exactly the same this as its direct parent, but checks the group on the view_groups field of
the object the object
""" """
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
res = super(CanViewMixin, self).dispatch(request, *arg, **kwargs) res = super(CanViewMixin, self).dispatch(request, *arg, **kwargs)
if ((hasattr(self, 'object') and (self.object is None or self.request.user.can_view(self.object))) or if hasattr(self, 'object'):
(hasattr(self, 'object_list') and (self.object_list is None or self.object_list is [] or self.request.user.can_view(self.object_list[0])))): obj = self.object
elif hasattr(self, 'object_list'):
obj = self.object_list[0] if self.object_list else None
if can_view(obj, self.request.user):
return res return res
try: # Always unlock when 403 try: # Always unlock when 403
self.object.unset_lock() self.object.unset_lock()
except: pass except: pass
print("CanViewMixin 403")
raise PermissionDenied raise PermissionDenied
from .user import * from .user import *

View File

@ -27,21 +27,21 @@ class UserPropForm(forms.ModelForm):
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = User model = User
fields = ['groups', 'edit_group', 'view_group'] fields = ['groups', 'edit_groups', 'view_groups']
labels = { labels = {
'edit_group': "Edit profile group", 'edit_groups': "Edit profile group",
'view_group': "View profile group", 'view_groups': "View profile group",
} }
help_texts = { help_texts = {
'edit_group': "Groups that can edit this user's profile", 'edit_groups': "Groups that can edit this user's profile",
'view_group': "Groups that can view this user's profile", 'view_groups': "Groups that can view this user's profile",
'groups': "Which groups this user belongs to", 'groups': "Which groups this user belongs to",
} }
widgets = { widgets = {
'groups': CheckboxSelectMultiple, 'groups': CheckboxSelectMultiple,
'user_permissions': CheckboxSelectMultiple, 'user_permissions': CheckboxSelectMultiple,
'edit_group': CheckboxSelectMultiple, 'edit_groups': CheckboxSelectMultiple,
'view_group': CheckboxSelectMultiple, 'view_groups': CheckboxSelectMultiple,
} }
class PagePropForm(forms.ModelForm): class PagePropForm(forms.ModelForm):
@ -49,16 +49,16 @@ class PagePropForm(forms.ModelForm):
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = Page model = Page
fields = ['parent', 'name', 'owner_group', 'edit_group', 'view_group', ] fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ]
widgets = { widgets = {
'edit_group': CheckboxSelectMultiple, 'edit_groups': CheckboxSelectMultiple,
'view_group': CheckboxSelectMultiple, 'view_groups': CheckboxSelectMultiple,
} }
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
super(PagePropForm, self).__init__(*arg, **kwargs) super(PagePropForm, self).__init__(*arg, **kwargs)
self.fields['edit_group'].required = False self.fields['edit_groups'].required = False
self.fields['view_group'].required = False self.fields['view_groups'].required = False
class GroupEditForm(forms.ModelForm): class GroupEditForm(forms.ModelForm):

View File

@ -9,7 +9,7 @@ from core.models import Page, PageRev, LockError
from core.views.forms import PagePropForm from core.views.forms import PagePropForm
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin
class PageListView(ListView): class PageListView(CanViewMixin, ListView):
model = Page model = Page
template_name = 'core/page_list.jinja' template_name = 'core/page_list.jinja'

View File

@ -93,6 +93,11 @@ TEMPLATES = [
"filters": { "filters": {
"markdown": "core.templatetags.renderer.markdown", "markdown": "core.templatetags.renderer.markdown",
}, },
"globals": {
"can_edit_prop": "core.views.can_edit_prop",
"can_edit": "core.views.can_edit",
"can_view": "core.views.can_view",
},
"bytecode_cache": { "bytecode_cache": {
"name": "default", "name": "default",
"backend": "django_jinja.cache.BytecodeCache", "backend": "django_jinja.cache.BytecodeCache",
@ -228,12 +233,11 @@ AE_SUBSCRIPTIONS = {
CLUB_ROLES = { CLUB_ROLES = {
10: 'Président', 10: 'Président',
9: 'Vice-Président', 9: 'Vice-Président',
8: 'Vice-Président',
7: 'Trésorier', 7: 'Trésorier',
5: 'Responsable com', 5: 'Responsable com',
4: 'Secrétaire', 4: 'Secrétaire',
3: 'Responsable info', 3: 'Responsable info',
2: 'Membre du bureau', 2: 'Membre du bureau',
1: 'Membre actif', 1: 'Membre actif',
0: 'Membre', 0: 'Curieux',
} }

View File

@ -20,8 +20,12 @@ class Subscriber(User):
class Meta: class Meta:
proxy = True proxy = True
def __init__(self, *args, **kwargs):
super(Subscriber, self).__init__(*args, **kwargs)
def is_subscribed(self): def is_subscribed(self):
return self.subscriptions.last().is_valid_now() s = self.subscriptions.last()
return s.is_valid_now() if s is not None else False
class Subscription(models.Model): class Subscription(models.Model):
member = models.ForeignKey(Subscriber, related_name='subscriptions') member = models.ForeignKey(Subscriber, related_name='subscriptions')

View File

@ -1,11 +1,22 @@
from django.shortcuts import render from django.shortcuts import render
from django.views.generic.edit import UpdateView, CreateView from django.views.generic.edit import UpdateView, CreateView
from django.views.generic.base import View
from django.core.exceptions import PermissionDenied
from django import forms from django import forms
from django.forms import Select from django.forms import Select
from django.conf import settings from django.conf import settings
from subscription.models import Subscriber, Subscription from subscription.models import Subscriber, Subscription
from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.models import User
class SubscriberMixin(View):
def dispatch(self, request, *arg, **kwargs):
res = super(SubscriberMixin, self).dispatch(request, *arg, **kwargs)
subscriber = Subscriber.objects.filter(pk=request.user.pk).first()
if subscriber is not None and subscriber.is_subscribed():
return ret
raise PermissionDenied
class SubscriptionForm(forms.ModelForm): class SubscriptionForm(forms.ModelForm):
class Meta: class Meta: