From ba1965add0fc9645376b372d95f17577eead485c Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 24 Aug 2023 15:02:30 -0400 Subject: [PATCH] Add profile model --- app/admin.py | 14 ++++++++++ app/migrations/0037_profile.py | 35 +++++++++++++++++++++++++ app/models/__init__.py | 1 + app/models/profile.py | 37 +++++++++++++++++++++++++++ app/templates/app/logged_in_base.html | 11 ++++++++ app/templatetags/settings.py | 9 +++++++ requirements.txt | 1 + webodm/settings.py | 10 ++++++++ 8 files changed, 118 insertions(+) create mode 100644 app/migrations/0037_profile.py create mode 100644 app/models/profile.py diff --git a/app/admin.py b/app/admin.py index 81848e19..7ad77d95 100644 --- a/app/admin.py +++ b/app/admin.py @@ -10,10 +10,13 @@ from django.http import HttpResponseRedirect from django.urls import reverse from django.utils.html import format_html from guardian.admin import GuardedModelAdmin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.models import User from app.models import PluginDatum from app.models import Preset from app.models import Plugin +from app.models import Profile from app.plugins import get_plugin_by_name, enable_plugin, disable_plugin, delete_plugin, valid_plugin, \ get_plugins_persistent_path, clear_plugins_cache, init_plugins from .models import Project, Task, Setting, Theme @@ -260,3 +263,14 @@ class PluginAdmin(admin.ModelAdmin): admin.site.register(Plugin, PluginAdmin) + +class ProfileInline(admin.StackedInline): + model = Profile + can_delete = False + +class UserAdmin(BaseUserAdmin): + inlines = [ProfileInline] + +# Re-register UserAdmin +admin.site.unregister(User) +admin.site.register(User, UserAdmin) diff --git a/app/migrations/0037_profile.py b/app/migrations/0037_profile.py new file mode 100644 index 00000000..ab7a1fa0 --- /dev/null +++ b/app/migrations/0037_profile.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.27 on 2023-08-24 16:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def create_profiles(apps, schema_editor): + User = apps.get_model('auth', 'User') + Profile = apps.get_model('app', 'Profile') + + for u in User.objects.all(): + p = Profile.objects.create(user=u) + p.save() + print("Created user profile for %s" % u.username) + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('app', '0036_task_size'), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quota', models.FloatField(blank=True, default=-1, help_text='Maximum disk quota in megabytes', verbose_name='Quota')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + + migrations.RunPython(create_profiles), + ] diff --git a/app/models/__init__.py b/app/models/__init__.py index b7434b5d..a9d64a24 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -5,6 +5,7 @@ from .theme import Theme from .setting import Setting from .plugin_datum import PluginDatum from .plugin import Plugin +from .profile import Profile # deprecated def image_directory_path(image_upload, filename): diff --git a/app/models/profile.py b/app/models/profile.py new file mode 100644 index 00000000..11adefee --- /dev/null +++ b/app/models/profile.py @@ -0,0 +1,37 @@ +from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.db.models.signals import post_save +from django.dispatch import receiver +from app.models import Task +from django.db.models import Sum +from django.core.cache import cache + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + quota = models.FloatField(default=-1, blank=True, help_text=_("Maximum disk quota in megabytes"), verbose_name=_("Quota")) + + def has_quota(self): + return self.quota != -1 + + def used_quota(self): + return Task.objects.filter(project__owner=self.user).aggregate(total=Sum('size'))['total'] + + def used_quota_cached(self): + k = f'used_quota_{self.user.id}' + cached = cache.get(k) + if cached is not None: + return cached + + v = self.used_quota() + cache.set(k, v, 300) # 2 minutes + return v + +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + instance.profile.save() \ No newline at end of file diff --git a/app/templates/app/logged_in_base.html b/app/templates/app/logged_in_base.html index c4d18f5c..2f8fe229 100644 --- a/app/templates/app/logged_in_base.html +++ b/app/templates/app/logged_in_base.html @@ -15,6 +15,17 @@ {% blocktrans with user=user.username %}Hello, {{ user }}!{% endblocktrans %}
{{ user.email }} + {% if user.profile.has_quota %} +
  • + {% with tot_quota=user.profile.quota %} + {% with used_quota=user.profile.used_quota_cached %} + {% percentage 0 tot_quota as perc_quota %} + + Tot: {{ tot_quota }} Used: {{ used_quota }} + Perc: {{ perc_quota|floatformat:0 }} + + {% endwith %}{% endwith %} + {% endif %}
  • {% trans 'Logout' %}
  • diff --git a/app/templatetags/settings.py b/app/templatetags/settings.py index 7bda2e18..f12ebf88 100644 --- a/app/templatetags/settings.py +++ b/app/templatetags/settings.py @@ -7,6 +7,15 @@ from webodm import settings register = template.Library() logger = logging.getLogger('app.logger') +@register.simple_tag +def percentage(num, den, maximum=None): + if den == 0: + return 0 + perc = max(0, num / den * 100) + if maximum is not None: + perc = min(perc, maximum) + return perc + @register.simple_tag def is_single_user_mode(): return settings.SINGLE_USER_MODE diff --git a/requirements.txt b/requirements.txt index 61aa2130..07a92e66 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ django-filter==2.4.0 django-guardian==1.4.9 django-imagekit==4.0.1 django-libsass==0.7 +django-redis==4.12.1 django-webpack-loader==0.6.0 djangorestframework==3.13.1 djangorestframework-jwt==1.9.0 diff --git a/webodm/settings.py b/webodm/settings.py index 943a4705..66bf8561 100644 --- a/webodm/settings.py +++ b/webodm/settings.py @@ -377,6 +377,16 @@ CELERY_INCLUDE=['worker.tasks', 'app.plugins.worker'] CELERY_WORKER_REDIRECT_STDOUTS = False CELERY_WORKER_HIJACK_ROOT_LOGGER = False +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": os.environ.get('WO_BROKER', 'redis://localhost'), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} + # Number of minutes a processing node hasn't been seen # before it should be considered offline NODE_OFFLINE_MINUTES = 5