arthexis 0.1.21__py3-none-any.whl → 0.1.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arthexis might be problematic. Click here for more details.
- {arthexis-0.1.21.dist-info → arthexis-0.1.22.dist-info}/METADATA +8 -8
- {arthexis-0.1.21.dist-info → arthexis-0.1.22.dist-info}/RECORD +31 -31
- config/settings.py +4 -0
- config/urls.py +5 -0
- core/admin.py +139 -19
- core/environment.py +2 -239
- core/models.py +419 -2
- core/system.py +76 -0
- core/tests.py +152 -8
- core/views.py +35 -1
- nodes/admin.py +148 -38
- nodes/apps.py +11 -0
- nodes/models.py +26 -6
- nodes/tests.py +214 -1
- nodes/views.py +1 -0
- ocpp/admin.py +20 -1
- ocpp/consumers.py +1 -0
- ocpp/models.py +23 -1
- ocpp/tasks.py +99 -1
- ocpp/tests.py +227 -2
- ocpp/views.py +281 -3
- pages/admin.py +112 -15
- pages/apps.py +32 -0
- pages/forms.py +31 -8
- pages/models.py +42 -2
- pages/tests.py +361 -22
- pages/urls.py +5 -0
- pages/views.py +264 -11
- {arthexis-0.1.21.dist-info → arthexis-0.1.22.dist-info}/WHEEL +0 -0
- {arthexis-0.1.21.dist-info → arthexis-0.1.22.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.21.dist-info → arthexis-0.1.22.dist-info}/top_level.txt +0 -0
pages/admin.py
CHANGED
|
@@ -595,7 +595,23 @@ class ViewHistoryAdmin(EntityModelAdmin):
|
|
|
595
595
|
)
|
|
596
596
|
|
|
597
597
|
def traffic_data_view(self, request):
|
|
598
|
-
return JsonResponse(
|
|
598
|
+
return JsonResponse(
|
|
599
|
+
self._build_chart_data(days=self._resolve_requested_days(request))
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
def _resolve_requested_days(self, request, default: int = 30) -> int:
|
|
603
|
+
raw_value = request.GET.get("days")
|
|
604
|
+
if raw_value in (None, ""):
|
|
605
|
+
return default
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
days = int(raw_value)
|
|
609
|
+
except (TypeError, ValueError):
|
|
610
|
+
return default
|
|
611
|
+
|
|
612
|
+
minimum = 1
|
|
613
|
+
maximum = 90
|
|
614
|
+
return max(minimum, min(days, maximum))
|
|
599
615
|
|
|
600
616
|
def _build_chart_data(self, days: int = 30, max_pages: int = 8) -> dict:
|
|
601
617
|
end_date = timezone.localdate()
|
|
@@ -689,11 +705,13 @@ class UserStoryAdmin(EntityModelAdmin):
|
|
|
689
705
|
actions = ["create_github_issues"]
|
|
690
706
|
list_display = (
|
|
691
707
|
"name",
|
|
708
|
+
"language_code",
|
|
692
709
|
"rating",
|
|
693
710
|
"path",
|
|
694
711
|
"status",
|
|
695
712
|
"submitted_at",
|
|
696
713
|
"github_issue_display",
|
|
714
|
+
"screenshot_display",
|
|
697
715
|
"take_screenshot",
|
|
698
716
|
"owner",
|
|
699
717
|
"assign_to",
|
|
@@ -703,6 +721,7 @@ class UserStoryAdmin(EntityModelAdmin):
|
|
|
703
721
|
"name",
|
|
704
722
|
"comments",
|
|
705
723
|
"path",
|
|
724
|
+
"language_code",
|
|
706
725
|
"referer",
|
|
707
726
|
"github_issue_url",
|
|
708
727
|
"ip_address",
|
|
@@ -715,6 +734,7 @@ class UserStoryAdmin(EntityModelAdmin):
|
|
|
715
734
|
"path",
|
|
716
735
|
"user",
|
|
717
736
|
"owner",
|
|
737
|
+
"language_code",
|
|
718
738
|
"referer",
|
|
719
739
|
"user_agent",
|
|
720
740
|
"ip_address",
|
|
@@ -722,6 +742,7 @@ class UserStoryAdmin(EntityModelAdmin):
|
|
|
722
742
|
"submitted_at",
|
|
723
743
|
"github_issue_number",
|
|
724
744
|
"github_issue_url",
|
|
745
|
+
"screenshot_display",
|
|
725
746
|
)
|
|
726
747
|
ordering = ("-submitted_at",)
|
|
727
748
|
fields = (
|
|
@@ -729,7 +750,9 @@ class UserStoryAdmin(EntityModelAdmin):
|
|
|
729
750
|
"rating",
|
|
730
751
|
"comments",
|
|
731
752
|
"take_screenshot",
|
|
753
|
+
"screenshot_display",
|
|
732
754
|
"path",
|
|
755
|
+
"language_code",
|
|
733
756
|
"user",
|
|
734
757
|
"owner",
|
|
735
758
|
"status",
|
|
@@ -758,6 +781,21 @@ class UserStoryAdmin(EntityModelAdmin):
|
|
|
758
781
|
)
|
|
759
782
|
if obj.github_issue_number is not None:
|
|
760
783
|
return f"#{obj.github_issue_number}"
|
|
784
|
+
return ""
|
|
785
|
+
|
|
786
|
+
@admin.display(description=_("Screenshot"), ordering="screenshot")
|
|
787
|
+
def screenshot_display(self, obj):
|
|
788
|
+
if not obj.screenshot_id:
|
|
789
|
+
return ""
|
|
790
|
+
try:
|
|
791
|
+
url = reverse("admin:nodes_contentsample_change", args=[obj.screenshot_id])
|
|
792
|
+
except NoReverseMatch:
|
|
793
|
+
return obj.screenshot.path
|
|
794
|
+
return format_html(
|
|
795
|
+
'<a href="{}" target="_blank" rel="noopener noreferrer">{}</a>',
|
|
796
|
+
url,
|
|
797
|
+
_("View screenshot"),
|
|
798
|
+
)
|
|
761
799
|
return _("Not created")
|
|
762
800
|
|
|
763
801
|
@admin.action(description=_("Create GitHub issues"))
|
|
@@ -842,34 +880,93 @@ def favorite_toggle(request, ct_id):
|
|
|
842
880
|
ct = get_object_or_404(ContentType, pk=ct_id)
|
|
843
881
|
fav = Favorite.objects.filter(user=request.user, content_type=ct).first()
|
|
844
882
|
next_url = request.GET.get("next")
|
|
845
|
-
if fav:
|
|
846
|
-
return redirect(next_url or "admin:favorite_list")
|
|
847
883
|
if request.method == "POST":
|
|
884
|
+
if fav and request.POST.get("remove"):
|
|
885
|
+
fav.delete()
|
|
886
|
+
return redirect(next_url or "admin:index")
|
|
848
887
|
label = request.POST.get("custom_label", "").strip()
|
|
849
888
|
user_data = request.POST.get("user_data") == "on"
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
889
|
+
priority_raw = request.POST.get("priority", "").strip()
|
|
890
|
+
if fav:
|
|
891
|
+
default_priority = fav.priority
|
|
892
|
+
else:
|
|
893
|
+
default_priority = 0
|
|
894
|
+
if priority_raw:
|
|
895
|
+
try:
|
|
896
|
+
priority = int(priority_raw)
|
|
897
|
+
except (TypeError, ValueError):
|
|
898
|
+
priority = default_priority
|
|
899
|
+
else:
|
|
900
|
+
priority = default_priority
|
|
901
|
+
|
|
902
|
+
if fav:
|
|
903
|
+
update_fields = []
|
|
904
|
+
if fav.custom_label != label:
|
|
905
|
+
fav.custom_label = label
|
|
906
|
+
update_fields.append("custom_label")
|
|
907
|
+
if fav.user_data != user_data:
|
|
908
|
+
fav.user_data = user_data
|
|
909
|
+
update_fields.append("user_data")
|
|
910
|
+
if fav.priority != priority:
|
|
911
|
+
fav.priority = priority
|
|
912
|
+
update_fields.append("priority")
|
|
913
|
+
if update_fields:
|
|
914
|
+
fav.save(update_fields=update_fields)
|
|
915
|
+
else:
|
|
916
|
+
Favorite.objects.create(
|
|
917
|
+
user=request.user,
|
|
918
|
+
content_type=ct,
|
|
919
|
+
custom_label=label,
|
|
920
|
+
user_data=user_data,
|
|
921
|
+
priority=priority,
|
|
922
|
+
)
|
|
856
923
|
return redirect(next_url or "admin:index")
|
|
857
924
|
return render(
|
|
858
925
|
request,
|
|
859
926
|
"admin/favorite_confirm.html",
|
|
860
|
-
{
|
|
927
|
+
{
|
|
928
|
+
"content_type": ct,
|
|
929
|
+
"favorite": fav,
|
|
930
|
+
"next": next_url,
|
|
931
|
+
"initial_label": fav.custom_label if fav else "",
|
|
932
|
+
"initial_priority": fav.priority if fav else 0,
|
|
933
|
+
"is_checked": fav.user_data if fav else True,
|
|
934
|
+
},
|
|
861
935
|
)
|
|
862
936
|
|
|
863
937
|
|
|
864
938
|
def favorite_list(request):
|
|
865
|
-
favorites =
|
|
866
|
-
|
|
939
|
+
favorites = (
|
|
940
|
+
Favorite.objects.filter(user=request.user)
|
|
941
|
+
.select_related("content_type")
|
|
942
|
+
.order_by("priority", "pk")
|
|
867
943
|
)
|
|
868
944
|
if request.method == "POST":
|
|
869
|
-
selected = request.POST.getlist("user_data")
|
|
945
|
+
selected = set(request.POST.getlist("user_data"))
|
|
870
946
|
for fav in favorites:
|
|
871
|
-
|
|
872
|
-
fav.
|
|
947
|
+
update_fields = []
|
|
948
|
+
user_selected = str(fav.pk) in selected
|
|
949
|
+
if fav.user_data != user_selected:
|
|
950
|
+
fav.user_data = user_selected
|
|
951
|
+
update_fields.append("user_data")
|
|
952
|
+
|
|
953
|
+
priority_raw = request.POST.get(f"priority_{fav.pk}", "").strip()
|
|
954
|
+
if priority_raw:
|
|
955
|
+
try:
|
|
956
|
+
priority = int(priority_raw)
|
|
957
|
+
except (TypeError, ValueError):
|
|
958
|
+
priority = fav.priority
|
|
959
|
+
else:
|
|
960
|
+
if fav.priority != priority:
|
|
961
|
+
fav.priority = priority
|
|
962
|
+
update_fields.append("priority")
|
|
963
|
+
else:
|
|
964
|
+
if fav.priority != 0:
|
|
965
|
+
fav.priority = 0
|
|
966
|
+
update_fields.append("priority")
|
|
967
|
+
|
|
968
|
+
if update_fields:
|
|
969
|
+
fav.save(update_fields=update_fields)
|
|
873
970
|
return redirect("admin:favorite_list")
|
|
874
971
|
return render(request, "admin/favorite_list.html", {"favorites": favorites})
|
|
875
972
|
|
pages/apps.py
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from django.apps import AppConfig
|
|
4
|
+
from django.db import DatabaseError
|
|
5
|
+
from django.db.backends.signals import connection_created
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
2
9
|
|
|
3
10
|
|
|
4
11
|
class PagesConfig(AppConfig):
|
|
5
12
|
default_auto_field = "django.db.models.BigAutoField"
|
|
6
13
|
name = "pages"
|
|
7
14
|
verbose_name = "7. Experience"
|
|
15
|
+
_view_history_purged = False
|
|
8
16
|
|
|
9
17
|
def ready(self): # pragma: no cover - import for side effects
|
|
10
18
|
from . import checks # noqa: F401
|
|
11
19
|
from . import site_config
|
|
12
20
|
|
|
13
21
|
site_config.ready()
|
|
22
|
+
connection_created.connect(
|
|
23
|
+
self._handle_connection_created,
|
|
24
|
+
dispatch_uid="pages_view_history_connection_created",
|
|
25
|
+
weak=False,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def _handle_connection_created(self, sender, connection, **kwargs):
|
|
29
|
+
if self._view_history_purged:
|
|
30
|
+
return
|
|
31
|
+
self._view_history_purged = True
|
|
32
|
+
self._purge_view_history()
|
|
33
|
+
|
|
34
|
+
def _purge_view_history(self, days: int = 15) -> None:
|
|
35
|
+
"""Remove stale :class:`pages.models.ViewHistory` entries."""
|
|
36
|
+
|
|
37
|
+
from .models import ViewHistory
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
deleted = ViewHistory.purge_older_than(days=days)
|
|
41
|
+
except DatabaseError:
|
|
42
|
+
logger.debug("Skipping view history purge; database unavailable", exc_info=True)
|
|
43
|
+
else:
|
|
44
|
+
if deleted:
|
|
45
|
+
logger.info("Purged %s view history entries older than %s days", deleted, days)
|
pages/forms.py
CHANGED
|
@@ -171,15 +171,35 @@ class UserStoryForm(forms.ModelForm):
|
|
|
171
171
|
"comments": forms.Textarea(attrs={"rows": 4, "maxlength": 400}),
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
def __init__(self, *args, **kwargs):
|
|
174
|
+
def __init__(self, *args, user=None, **kwargs):
|
|
175
|
+
self.user = user
|
|
175
176
|
super().__init__(*args, **kwargs)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
|
|
178
|
+
if user is not None and user.is_authenticated:
|
|
179
|
+
name_field = self.fields["name"]
|
|
180
|
+
name_field.required = False
|
|
181
|
+
name_field.label = _("Username")
|
|
182
|
+
name_field.initial = (user.get_username() or "")[:40]
|
|
183
|
+
name_field.widget.attrs.update(
|
|
184
|
+
{
|
|
185
|
+
"maxlength": 40,
|
|
186
|
+
"readonly": "readonly",
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
self.fields["name"] = forms.EmailField(
|
|
191
|
+
label=_("Email address"),
|
|
192
|
+
max_length=40,
|
|
193
|
+
required=True,
|
|
194
|
+
widget=forms.EmailInput(
|
|
195
|
+
attrs={
|
|
196
|
+
"maxlength": 40,
|
|
197
|
+
"placeholder": _("name@example.com"),
|
|
198
|
+
"autocomplete": "email",
|
|
199
|
+
"inputmode": "email",
|
|
200
|
+
}
|
|
201
|
+
),
|
|
202
|
+
)
|
|
183
203
|
self.fields["take_screenshot"].initial = True
|
|
184
204
|
self.fields["rating"].widget = forms.RadioSelect(
|
|
185
205
|
choices=[(i, str(i)) for i in range(1, 6)]
|
|
@@ -194,5 +214,8 @@ class UserStoryForm(forms.ModelForm):
|
|
|
194
214
|
return comments
|
|
195
215
|
|
|
196
216
|
def clean_name(self):
|
|
217
|
+
if self.user is not None and self.user.is_authenticated:
|
|
218
|
+
return (self.user.get_username() or "")[:40]
|
|
219
|
+
|
|
197
220
|
name = (self.cleaned_data.get("name") or "").strip()
|
|
198
221
|
return name[:40]
|
pages/models.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import logging
|
|
3
|
+
from datetime import timedelta
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from django.db import models
|
|
@@ -7,10 +8,11 @@ from django.db.models import Q
|
|
|
7
8
|
from core.entity import Entity
|
|
8
9
|
from core.models import Lead, SecurityGroup
|
|
9
10
|
from django.contrib.sites.models import Site
|
|
10
|
-
from nodes.models import NodeRole
|
|
11
|
+
from nodes.models import ContentSample, NodeRole
|
|
11
12
|
from django.apps import apps as django_apps
|
|
13
|
+
from django.utils import timezone
|
|
12
14
|
from django.utils.text import slugify
|
|
13
|
-
from django.utils.translation import gettext, gettext_lazy as _
|
|
15
|
+
from django.utils.translation import gettext, gettext_lazy as _, get_language_info
|
|
14
16
|
from importlib import import_module
|
|
15
17
|
from django.urls import URLPattern
|
|
16
18
|
from django.conf import settings
|
|
@@ -490,6 +492,14 @@ class ViewHistory(Entity):
|
|
|
490
492
|
def __str__(self) -> str: # pragma: no cover - simple representation
|
|
491
493
|
return f"{self.method} {self.path} ({self.status_code})"
|
|
492
494
|
|
|
495
|
+
@classmethod
|
|
496
|
+
def purge_older_than(cls, *, days: int) -> int:
|
|
497
|
+
"""Delete history entries recorded more than ``days`` days ago."""
|
|
498
|
+
|
|
499
|
+
cutoff = timezone.now() - timedelta(days=days)
|
|
500
|
+
deleted, _ = cls.objects.filter(visited_at__lt=cutoff).delete()
|
|
501
|
+
return deleted
|
|
502
|
+
|
|
493
503
|
|
|
494
504
|
class Favorite(Entity):
|
|
495
505
|
user = models.ForeignKey(
|
|
@@ -500,9 +510,11 @@ class Favorite(Entity):
|
|
|
500
510
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
|
501
511
|
custom_label = models.CharField(max_length=100, blank=True)
|
|
502
512
|
user_data = models.BooleanField(default=False)
|
|
513
|
+
priority = models.IntegerField(default=0)
|
|
503
514
|
|
|
504
515
|
class Meta:
|
|
505
516
|
unique_together = ("user", "content_type")
|
|
517
|
+
ordering = ["priority", "pk"]
|
|
506
518
|
|
|
507
519
|
|
|
508
520
|
class UserStory(Lead):
|
|
@@ -545,6 +557,19 @@ class UserStory(Lead):
|
|
|
545
557
|
blank=True,
|
|
546
558
|
help_text=_("Link to the GitHub issue created for this feedback."),
|
|
547
559
|
)
|
|
560
|
+
screenshot = models.ForeignKey(
|
|
561
|
+
ContentSample,
|
|
562
|
+
on_delete=models.SET_NULL,
|
|
563
|
+
blank=True,
|
|
564
|
+
null=True,
|
|
565
|
+
related_name="user_stories",
|
|
566
|
+
help_text=_("Screenshot captured for this feedback."),
|
|
567
|
+
)
|
|
568
|
+
language_code = models.CharField(
|
|
569
|
+
max_length=15,
|
|
570
|
+
blank=True,
|
|
571
|
+
help_text=_("Language selected when the feedback was submitted."),
|
|
572
|
+
)
|
|
548
573
|
|
|
549
574
|
class Meta:
|
|
550
575
|
ordering = ["-submitted_at"]
|
|
@@ -590,6 +615,21 @@ class UserStory(Lead):
|
|
|
590
615
|
f"**Screenshot requested:** {screenshot_requested}",
|
|
591
616
|
]
|
|
592
617
|
|
|
618
|
+
language_code = (self.language_code or "").strip()
|
|
619
|
+
if language_code:
|
|
620
|
+
normalized = language_code.replace("_", "-").lower()
|
|
621
|
+
try:
|
|
622
|
+
info = get_language_info(normalized)
|
|
623
|
+
except KeyError:
|
|
624
|
+
language_display = ""
|
|
625
|
+
else:
|
|
626
|
+
language_display = info.get("name_local") or info.get("name") or ""
|
|
627
|
+
|
|
628
|
+
if language_display:
|
|
629
|
+
lines.append(f"**Language:** {language_display} ({normalized})")
|
|
630
|
+
else:
|
|
631
|
+
lines.append(f"**Language:** {normalized}")
|
|
632
|
+
|
|
593
633
|
if self.submitted_at:
|
|
594
634
|
lines.append(f"**Submitted at:** {self.submitted_at.isoformat()}")
|
|
595
635
|
|