Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hansegucker/AlekSIS-Core
  • pinguin/AlekSIS-Core
  • AlekSIS/official/AlekSIS-Core
  • sunweaver/AlekSIS-Core
  • sggua/AlekSIS-Core
  • edward/AlekSIS-Core
  • magicfelix/AlekSIS-Core
7 results
Show changes
Commits on Source (31)
# Generated by Django 3.1.3 on 2020-11-16 13:04
from django.db import migrations, models
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
('core', '0004_add_permissions_for_group_stats'),
]
operations = [
migrations.AddField(
model_name='activity',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
),
migrations.AddField(
model_name='activity',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
),
migrations.AddField(
model_name='notification',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
),
migrations.AddField(
model_name='notification',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
),
]
......@@ -19,7 +19,6 @@ from django.views.generic import CreateView, UpdateView
from django.views.generic.edit import DeleteView, ModelFormMixin
import reversion
from easyaudit.models import CRUDEvent
from guardian.admin import GuardedModelAdmin
from jsonstore.fields import IntegerField, JSONFieldMixin
from material.base import Layout, LayoutNode
......@@ -54,8 +53,7 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
This base model ensures all objects in AlekSIS apps fulfill the
following properties:
* crud_events property to retrieve easyaudit's CRUD event log
* created_at and updated_at properties based n CRUD events
* `versions` property to retrieve all versions of the model from reversion
* Allow injection of fields and code from AlekSIS apps to extend
model functionality.
......@@ -109,50 +107,30 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
pass
@property
def crud_events(self) -> QuerySet:
"""Get all CRUD events connected to this object from easyaudit."""
content_type = ContentType.objects.get_for_model(self)
def versions(self) -> List[Tuple[str, Tuple[Any, Any]]]:
"""Get all versions of this object from django-reversion.
return CRUDEvent.objects.filter(
object_id=self.pk, content_type=content_type
).select_related("user", "user__person")
Includes diffs to previous version.
"""
versions = reversion.models.Version.objects.get_for_object(self)
@property
def crud_event_create(self) -> Optional[CRUDEvent]:
"""Return create event of this object."""
return self.crud_events.filter(event_type=CRUDEvent.CREATE).latest("datetime")
versions_with_changes = []
for i, version in enumerate(versions):
diff = {}
if i > 0:
prev_version = versions[i - 1]
@property
def crud_event_update(self) -> Optional[CRUDEvent]:
"""Return last event of this object."""
return self.crud_events.latest("datetime")
for k, val in version.field_dict.items():
prev_val = prev_version.field_dict.get(k, None)
if prev_val != val:
diff[k] = (prev_val, val)
@property
def created_at(self) -> Optional[datetime]:
"""Determine creation timestamp from CRUD log."""
if self.crud_event_create:
return self.crud_event_create.datetime
versions_with_changes.append((version, diff))
@property
def updated_at(self) -> Optional[datetime]:
"""Determine last timestamp from CRUD log."""
if self.crud_event_update:
return self.crud_event_update.datetime
return versions_with_changes
extended_data = JSONField(default=dict, editable=False)
@property
def created_by(self) -> Optional[models.Model]:
"""Determine user who created this object from CRUD log."""
if self.crud_event_create:
return self.crud_event_create.user
@property
def updated_by(self) -> Optional[models.Model]:
"""Determine user who last updated this object from CRUD log."""
if self.crud_event_update:
return self.crud_event_update.user
extended_data = JSONField(default=dict, editable=False)
@classmethod
......
......@@ -21,6 +21,7 @@ from django.utils.translation import gettext_lazy as _
import jsonstore
from cache_memoize import cache_memoize
from dynamic_preferences.models import PerInstancePreferenceModel
from model_utils.models import TimeStampedModel
from phonenumber_field.modelfields import PhoneNumberField
from polymorphic.models import PolymorphicModel
......@@ -226,9 +227,9 @@ class Person(ExtensibleModel):
def age_at(self, today):
if self.date_of_birth:
years = today.year - self.date_of_birth.year
if (self.date_of_birth.month > today.month
or (self.date_of_birth.month == today.month
and self.date_of_birth.day > today.day)):
if self.date_of_birth.month > today.month or (
self.date_of_birth.month == today.month and self.date_of_birth.day > today.day
):
years -= 1
return years
......@@ -388,14 +389,14 @@ class Group(SchoolTermRelatedExtensibleModel):
""" Get stats about a given group """
stats = {}
stats['members'] = len(self.members.all())
stats["members"] = len(self.members.all())
ages = [person.age for person in self.members.filter(date_of_birth__isnull=False)]
if ages:
stats['age_avg'] = sum(ages) / len(ages)
stats['age_range_min'] = min(ages)
stats['age_range_max'] = max(ages)
stats["age_avg"] = sum(ages) / len(ages)
stats["age_range_min"] = min(ages)
stats["age_range_max"] = max(ages)
return stats
......@@ -440,7 +441,7 @@ class PersonGroupThrough(ExtensibleModel):
setattr(self, field_name, field_instance)
class Activity(ExtensibleModel):
class Activity(ExtensibleModel, TimeStampedModel):
"""Activity of a user to trace some actions done in AlekSIS in displayable form."""
user = models.ForeignKey(
......@@ -460,7 +461,7 @@ class Activity(ExtensibleModel):
verbose_name_plural = _("Activities")
class Notification(ExtensibleModel):
class Notification(ExtensibleModel, TimeStampedModel):
"""Notification to submit to a user."""
sender = models.CharField(max_length=100, verbose_name=_("Sender"))
......
......@@ -64,7 +64,6 @@ INSTALLED_APPS = [
"dbbackup",
"settings_context_processor",
"sass_processor",
"easyaudit",
"django_any_js",
"django_yarnpkg",
"django_tables2",
......@@ -129,7 +128,6 @@ MIDDLEWARE = [
"impersonate.middleware.ImpersonateMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"easyaudit.middleware.easyaudit.EasyAuditMiddleware",
"maintenance_mode.middleware.MaintenanceModeMiddleware",
"aleksis.core.util.middlewares.EnsurePersonMiddleware",
"django_prometheus.middleware.PrometheusAfterMiddleware",
......
......@@ -48,7 +48,7 @@
<span class="badge new primary-color">{{ activity.app }}</span>
<span class="title">{{ activity.title }}</span>
<p>
<i class="material-icons left">access_time</i> {{ activity.created_at }}
<i class="material-icons left">access_time</i> {{ activity.created }}
</p>
<p>
{{ activity.description }}
......@@ -71,7 +71,7 @@
<span class="badge new primary-color">{{ notification.app }}</span>
<span class="title">{{ notification.title }}</span>
<p>
<i class="material-icons left">access_time</i> {{ notification.created_at }}
<i class="material-icons left">access_time</i> {{ notification.created }}
</p>
<p>
{{ notification.description }}
......
{% load i18n data_helpers %}
<ul class="collection">
{% for event in obj.crud_events %}
{% if no_m2m and event.event_type == event.M2M_CHANGE or event.event_type == event.M2M_CHANGE_REV %}
{% else %}
<li class="collection-item">
<strong>
{% if event.event_type == event.CREATE %}
{% blocktrans with person=event.user.person %}
Created by {{ person }}
{% endblocktrans %}
{% elif event.event_type == event.UPDATE %}
{% blocktrans with person=event.user.person %}
Updated by {{ person }}
{% endblocktrans %}
{% elif event.event_type == event.DELETE %}
{% blocktrans with person=event.user.person %}
Deleted by {{ person }}
{% endblocktrans %}
{% elif event.event_type == event.M2M_CHANGE %}
{% blocktrans with person=event.user.person %}
Updated by {{ person }}
{% endblocktrans %}
{% elif event.event_type == event.M2M_CHANGE_REV %}
{% blocktrans with person=event.user.person %}
Updated by {{ person }}
{% endblocktrans %}
{% endif %}
</strong>
<div class="left" style="margin-right: 10px;">
{% if event.event_type == event.CREATE %}
<i class="material-icons">add_circle</i>
{% elif event.event_type == event.UPDATE %}
<i class="material-icons">edit</i>
{% elif event.event_type == event.DELETE %}
<i class="material-icons">delete</i>
{% elif event.event_type == event.M2M_CHANGE %}
<i class="material-icons">edit</i>
{% elif event.event_type == event.M2M_CHANGE_REV %}
<i class="material-icons">edit</i>
{% endif %}
</div>
<div class="right">
{{ event.datetime }}
</div>
{% parse_json event.changed_fields as changed_fields %}
{% if changed_fields %}
<ul>
{% for field, change in changed_fields.items %}
{% verbose_name event.content_type.app_label event.content_type.model field as verbose_name %}
<li>
{{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }}
</li>
{% endfor %}
</ul>
<div class="collection">
{% for version in obj.versions %}
<div class="collection-item">
<div class="left" style="margin-right: 10px;">
{% if forloop.first %}
<i class="material-icons">add_circle</i>
{% else %}
<i class="material-icons">edit</i>
{% endif %}
</li>
{% endif %}
</div>
<strong>
{{ version.0.revision.get_comment }}
{% trans "Changed by" %} {% firstof version.0.revision.user.person _("Unknown") %}
</strong>
<div class="right">
{{ version.0.revision.date_created }}
</div>
{% if version.1 %}
<ul>
{% for field, change in version.1.items %}
{% verbose_name version.0.content_type.app_label version.0.content_type.model field as verbose_name %}
<li>
{{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
</ul>
</div>
......@@ -15,7 +15,7 @@
{% trans "More information" %} → {{ notification.link }}
{% endif %}
{% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
{% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}
Sent by {{ trans_sender }} at {{ trans_created_at }}
{% endblocktrans %}
......@@ -37,7 +37,7 @@
</blockquote>
<p>
{% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
{% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}
Sent by {{ trans_sender }} at {{ trans_created_at }}
{% endblocktrans %}
</p>
......
import os
import sys
import time
from datetime import datetime, timedelta
from importlib import import_module
......@@ -7,9 +8,9 @@ from operator import itemgetter
from typing import Any, Callable, Optional, Sequence, Union
from uuid import uuid4
try:
if sys.version_info >= (3, 9):
from importlib import metadata
except ImportError:
else:
import importlib_metadata as metadata
from django.conf import settings
......
......@@ -17,6 +17,7 @@ from haystack.inputs import AutoQuery
from haystack.query import SearchQuerySet
from haystack.views import SearchView
from health_check.views import MainView
from reversion import set_user
from rules.contrib.views import PermissionRequiredMixin, permission_required
from .filters import GroupFilter, PersonFilter
......@@ -308,6 +309,7 @@ def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse
if request.method == "POST":
if edit_person_form.is_valid():
with reversion.create_revision():
set_user(request.user)
edit_person_form.save(commit=True)
messages.success(request, _("The person has been saved."))
......@@ -344,6 +346,7 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
if request.method == "POST":
if edit_group_form.is_valid():
with reversion.create_revision():
set_user(request.user)
group = edit_group_form.save(commit=True)
messages.success(request, _("The group has been saved."))
......@@ -543,6 +546,7 @@ def delete_person(request: HttpRequest, id_: int) -> HttpResponse:
person = objectgetter_optional(Person)(request, id_)
with reversion.create_revision():
set_user(request.user)
person.save()
person.delete()
......@@ -556,6 +560,7 @@ def delete_group(request: HttpRequest, id_: int) -> HttpResponse:
"""View to delete an group."""
group = objectgetter_optional(Group)(request, id_)
with reversion.create_revision():
set_user(request.user)
group.save()
group.delete()
......
This diff is collapsed.
......@@ -37,11 +37,10 @@ python = "^3.7"
Django = "^3.0"
django-any-js = "^1.0"
django-debug-toolbar = "^2.0"
django-easy-audit = {version ="^1.2rc1", allow-prereleases = true}
django-middleware-global-request = "^0.1.2"
django-menu-generator = "^1.0.4"
django-tables2 = "^2.1"
Pillow = "^7.0"
Pillow = "^8.0"
django-phonenumber-field = {version = "<5.1", extras = ["phonenumbers"]}
django-sass-processor = "^0.8"
libsass = "^0.20.0"
......@@ -69,8 +68,8 @@ html2text = "^2020.0.0"
django-ckeditor = "^6.0.0"
django-js-reverse = "^0.9.1"
calendarweek = "^0.4.3"
Celery = {version="^4.4.0", optional=true, extras=["django", "redis"]}
django-celery-results = {version="^1.1.2", optional=true}
Celery = {version="^5.0.0", optional=true, extras=["django", "redis"]}
django-celery-results = {version="^2.0.0", optional=true}
django-celery-beat = {version="^2.0.0", optional=true}
django-celery-email = {version="^3.0.0", optional=true}
django-jsonstore = "^0.4.1"
......@@ -91,7 +90,8 @@ django-health-check = "^3.12.1"
psutil = "^5.7.0"
celery-progress = "^0.0.14"
django-prometheus = "^2.1.0"
importlib-metadata = {version = "^2.0.0", python = "<3.8"}
importlib-metadata = {version = "^3.0.0", python = "<3.9"}
django-model-utils = "^4.0.0"
[tool.poetry.extras]
ldap = ["django-auth-ldap"]
......
......@@ -49,9 +49,8 @@ exclude = migrations,tests
ignore = BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05
[isort]
profile = black
line_length = 100
multi_line_output = 3
include_trailing_comma = 1
default_section = THIRDPARTY
known_first_party = aleksis
known_django = django
......