From bb575579971b7ba1da8e21c686c9a44a48f4d54d Mon Sep 17 00:00:00 2001 From: bashmak Date: Tue, 13 Feb 2018 18:02:00 +0300 Subject: [PATCH] New feauture: User flags. You may mark each user for multiple flags. --- abonapp/forms.py | 11 +++++ abonapp/migrations/0006_abon_markers.py | 23 +++++++++++ abonapp/models.py | 38 +++++++++++++++--- abonapp/templates/abonapp/editAbon.html | 17 ++++++++ abonapp/templates/abonapp/group_list.html | 2 +- .../templates/abonapp/modal_user_markers.html | 31 ++++++++++++++ abonapp/templates/abonapp/peoples.html | 2 +- abonapp/urls_abon.py | 2 + abonapp/views.py | 30 +++++++++++++- requirements.txt | 1 + static/css/custom.css | 19 +++++++++ static/img/user_markers.png | Bin 0 -> 11052 bytes 12 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 abonapp/migrations/0006_abon_markers.py create mode 100644 abonapp/templates/abonapp/modal_user_markers.html create mode 100644 static/img/user_markers.png diff --git a/abonapp/forms.py b/abonapp/forms.py index 9068eda..acfa9e8 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -7,6 +7,7 @@ from random import choice from string import digits, ascii_lowercase from . import models from django.conf import settings +from bitfield.forms import BitFieldCheckboxSelectMultiple TELEPHONE_REGEXP = getattr(settings, 'TELEPHONE_REGEXP', r'^\+[7,8,9,3]\d{10,11}$') @@ -179,3 +180,13 @@ class ExportUsersForm(forms.Form): fields = forms.MultipleChoiceField(choices=FIELDS_CHOICES, widget=forms.CheckboxSelectMultiple(attrs={"checked": ""}), label=_('Fields')) + + +class MarkersForm(forms.ModelForm): + class Meta: + model = models.Abon + fields = ['markers'] + + def save(self, commit=True): + instance = super(MarkersForm, self).save(commit=False) + return instance.save(update_fields=['markers']) diff --git a/abonapp/migrations/0006_abon_markers.py b/abonapp/migrations/0006_abon_markers.py new file mode 100644 index 0000000..ba76123 --- /dev/null +++ b/abonapp/migrations/0006_abon_markers.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-02-13 14:06 +from __future__ import unicode_literals + +import bitfield.models +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('abonapp', '0005_auto_20180123_1353'), + ] + + operations = [ + migrations.AddField( + model_name='abon', + name='markers', + field=bitfield.models.BitField((('icon_donkey', 'Donkey'), ('icon_fire', 'Fire'), ('icon_ok', 'Ok'), + ('icon_king', 'King'), ('icon_tv', 'TV'), ('icon_smile', 'Smile'), + ('icon_dollar', 'Dollar'), ('icon_service', 'Service'), + ('icon_mrk', 'Marker')), default=None), + ), + ] diff --git a/abonapp/models.py b/abonapp/models.py index 676d23a..f329859 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -1,17 +1,20 @@ from datetime import datetime + +from django.conf import settings +from django.core import validators from django.core.exceptions import ValidationError from django.core.validators import RegexValidator +from django.db import models, connection, transaction from django.db.models.signals import post_save, post_delete, pre_delete, post_init from django.dispatch import receiver from django.utils import timezone -from django.db import models, connection, transaction -from django.core import validators -from django.utils.translation import ugettext as _ -from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError -from tariff_app.models import Tariff, PeriodicPay +from django.utils.translation import ugettext_lazy as _ + from accounts_app.models import UserProfile, MyUserManager +from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex -from django.conf import settings +from tariff_app.models import Tariff, PeriodicPay +from bitfield import BitField TELEPHONE_REGEXP = getattr(settings, 'TELEPHONE_REGEXP', r'^\+[7,8,9,3]\d{10,11}$') @@ -159,6 +162,29 @@ class Abon(UserProfile): dev_port = models.ForeignKey('devapp.Port', null=True, blank=True, on_delete=models.SET_NULL) is_dynamic_ip = models.BooleanField(default=False) + MARKER_FLAGS = ( + ('icon_donkey', _('Donkey')), + ('icon_fire', _('Fire')), + ('icon_ok', _('Ok')), + ('icon_king', _('King')), + ('icon_tv', _('TV')), + ('icon_smile', _('Smile')), + ('icon_dollar', _('Dollar')), + ('icon_service', _('Service')), + ('icon_mrk', _('Marker')) + ) + markers = BitField(flags=MARKER_FLAGS, default=0) + + def get_flag_icons(self): + """ + Return icon list of set flags from self.markers + :return: ['m-icon-donkey', 'm-icon-tv', ...] + """ + return ["m-%s" % name for name, state in self.markers if state] + + def is_markers_empty(self): + return int(self.markers) == 0 + def active_tariff(self): return self.current_tariff diff --git a/abonapp/templates/abonapp/editAbon.html b/abonapp/templates/abonapp/editAbon.html index 6a95b76..bde8882 100644 --- a/abonapp/templates/abonapp/editAbon.html +++ b/abonapp/templates/abonapp/editAbon.html @@ -216,6 +216,23 @@ {% endif %} +
+
+

{% trans 'User flags' %}

+
+
+ {% for user_icon in abon.get_flag_icons %} + + {% endfor %} +
+ +
diff --git a/abonapp/templates/abonapp/group_list.html b/abonapp/templates/abonapp/group_list.html index 761efec..175c42f 100644 --- a/abonapp/templates/abonapp/group_list.html +++ b/abonapp/templates/abonapp/group_list.html @@ -83,6 +83,6 @@ - {% include 'toolbar_page_cbv.html' %} + {% include 'pagination.html' %} {% endblock %} diff --git a/abonapp/templates/abonapp/modal_user_markers.html b/abonapp/templates/abonapp/modal_user_markers.html new file mode 100644 index 0000000..934535b --- /dev/null +++ b/abonapp/templates/abonapp/modal_user_markers.html @@ -0,0 +1,31 @@ +{% load i18n %} +
{% csrf_token %} + + + + + + +
diff --git a/abonapp/templates/abonapp/peoples.html b/abonapp/templates/abonapp/peoples.html index 0632827..bc52466 100644 --- a/abonapp/templates/abonapp/peoples.html +++ b/abonapp/templates/abonapp/peoples.html @@ -167,6 +167,6 @@ - {% include 'toolbar_page_cbv.html' %} + {% include 'pagination.html' %} {% endblock %} diff --git a/abonapp/urls_abon.py b/abonapp/urls_abon.py index 755b33f..404889c 100644 --- a/abonapp/urls_abon.py +++ b/abonapp/urls_abon.py @@ -41,6 +41,8 @@ urlpatterns = [ url(r'^(?P\d+)/tel/add$', views.tel_add, name='telephone_new'), url(r'^(?P\d+)/tel/del$', views.tel_del, name='telephone_del'), + url(r'^(?P\d+)/markers$', views.EditSibscriberMarkers.as_view(), name='markers_edit'), + url(r'^(?P\d+)/periodic_pay$', views.add_edit_periodic_pay, name='add_periodic_pay'), url(r'^(?P\d+)/periodic_pay(?P\d+)$', views.add_edit_periodic_pay, name='add_periodic_pay'), url(r'^(?P\d+)/periodic_pay(?P\d+)/del$', views.del_periodic_pay, name='del_periodic_pay') diff --git a/abonapp/views.py b/abonapp/views.py index b651e57..7f1d7e8 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -10,7 +10,7 @@ from django.http import HttpResponse, HttpResponseBadRequest from django.contrib import messages from django.utils.translation import gettext_lazy as _ from django.utils.decorators import method_decorator -from django.views.generic import ListView +from django.views.generic import ListView, UpdateView from django.conf import settings from statistics.models import StatCache @@ -1036,6 +1036,34 @@ def del_periodic_pay(request, gid, uid, periodic_pay_id): return redirect('abonapp:abon_services', gid, uid) +@method_decorator([login_required, mydefs.only_admins], name='dispatch') +class EditSibscriberMarkers(UpdateView): + http_method_names = ['get', 'post'] + template_name = 'abonapp/modal_user_markers.html' + form_class = forms.MarkersForm + + def get_object(self, queryset=None): + obj = models.Abon.objects.get(pk=self.kwargs.get('uid')) + return obj + + def get_success_url(self): + return resolve_url('abonapp:abon_home', self.kwargs.get('gid'), self.kwargs.get('uid')) + + def get_context_data(self, **kwargs): + context = super(EditSibscriberMarkers, self).get_context_data(**kwargs) + context['gid'] = self.kwargs.get('gid') + context['uid'] = self.kwargs.get('uid') + return context + + def form_invalid(self, form): + messages.add_message(self.request, messages.ERROR, _('fix form errors')) + return super(EditSibscriberMarkers, self).form_invalid(form) + + def form_valid(self, form): + v = super(EditSibscriberMarkers, self).form_valid(form) + messages.add_message(self.request, messages.SUCCESS, _('User flags has changed successfully')) + return v + # API's diff --git a/requirements.txt b/requirements.txt index 6f17527..4b8ad89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ requests webdavclient pyst2 django-jsonview +django-bitfield diff --git a/static/css/custom.css b/static/css/custom.css index ade7ac7..4e12389 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -280,3 +280,22 @@ pre { word-wrap: break-word; } +/* + * Markers icons + */ +.m-icon{ + background-image: url(../img/user_markers.png); + width: 26px; + height: 26px; + display: inline-block; + margin-bottom: -9px; +} +.m-icon_donkey{background-position: 0 0;} +.m-icon_fire{background-position: -26px 0;} +.m-icon_ok{background-position: -52px 0;} +.m-icon_king{background-position: 0 -26px;} +.m-icon_tv{background-position: -26px -26px;} +.m-icon_smile{background-position: -52px -26px;} +.m-icon_dollar{background-position: 0 -52px;} +.m-icon_service{background-position: -26px -52px;} +.m-icon_mrk{background-position: -52px -52px;} diff --git a/static/img/user_markers.png b/static/img/user_markers.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb8fdc7bc38d422f5ba8d90067f6fa7fa948af5 GIT binary patch literal 11052 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 m