arthexis 0.1.14__py3-none-any.whl → 0.1.16__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.14.dist-info → arthexis-0.1.16.dist-info}/METADATA +3 -2
- {arthexis-0.1.14.dist-info → arthexis-0.1.16.dist-info}/RECORD +41 -39
- config/urls.py +5 -0
- core/admin.py +200 -9
- core/admindocs.py +44 -3
- core/apps.py +1 -1
- core/backends.py +44 -8
- core/entity.py +17 -1
- core/github_issues.py +12 -7
- core/log_paths.py +24 -10
- core/mailer.py +9 -5
- core/models.py +92 -23
- core/release.py +173 -2
- core/system.py +411 -4
- core/tasks.py +5 -1
- core/test_system_info.py +16 -0
- core/tests.py +280 -0
- core/views.py +252 -38
- nodes/admin.py +25 -1
- nodes/models.py +99 -6
- nodes/rfid_sync.py +15 -0
- nodes/tests.py +142 -3
- nodes/utils.py +3 -0
- ocpp/consumers.py +38 -0
- ocpp/models.py +19 -4
- ocpp/tasks.py +156 -2
- ocpp/test_rfid.py +44 -2
- ocpp/tests.py +111 -1
- pages/admin.py +188 -5
- pages/context_processors.py +20 -1
- pages/middleware.py +4 -0
- pages/models.py +39 -1
- pages/module_defaults.py +156 -0
- pages/tasks.py +74 -0
- pages/tests.py +629 -8
- pages/urls.py +2 -0
- pages/utils.py +11 -0
- pages/views.py +106 -38
- {arthexis-0.1.14.dist-info → arthexis-0.1.16.dist-info}/WHEEL +0 -0
- {arthexis-0.1.14.dist-info → arthexis-0.1.16.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.14.dist-info → arthexis-0.1.16.dist-info}/top_level.txt +0 -0
pages/urls.py
CHANGED
|
@@ -6,7 +6,9 @@ app_name = "pages"
|
|
|
6
6
|
|
|
7
7
|
urlpatterns = [
|
|
8
8
|
path("", views.index, name="index"),
|
|
9
|
+
path("readme/", views.readme, name="readme"),
|
|
9
10
|
path("sitemap.xml", views.sitemap, name="pages-sitemap"),
|
|
11
|
+
path("release/", views.release_admin_redirect, name="release-admin"),
|
|
10
12
|
path("client-report/", views.client_report, name="client-report"),
|
|
11
13
|
path("release-checklist", views.release_checklist, name="release-checklist"),
|
|
12
14
|
path("login/", views.login_view, name="login"),
|
pages/utils.py
CHANGED
|
@@ -10,3 +10,14 @@ def landing(label=None):
|
|
|
10
10
|
return view
|
|
11
11
|
|
|
12
12
|
return decorator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def landing_leads_supported() -> bool:
|
|
16
|
+
"""Return ``True`` when the local node supports landing lead tracking."""
|
|
17
|
+
|
|
18
|
+
from nodes.models import Node
|
|
19
|
+
|
|
20
|
+
node = Node.get_local()
|
|
21
|
+
if not node:
|
|
22
|
+
return False
|
|
23
|
+
return node.has_feature("celery-queue")
|
pages/views.py
CHANGED
|
@@ -19,12 +19,14 @@ from django.contrib.auth.tokens import default_token_generator
|
|
|
19
19
|
from django.contrib.auth.views import LoginView
|
|
20
20
|
from django import forms
|
|
21
21
|
from django.apps import apps as django_apps
|
|
22
|
+
from utils.decorators import security_group_required
|
|
22
23
|
from utils.sites import get_site
|
|
23
24
|
from django.http import Http404, HttpResponse, JsonResponse
|
|
24
25
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
25
26
|
from nodes.models import Node
|
|
27
|
+
from django.template import loader
|
|
26
28
|
from django.template.response import TemplateResponse
|
|
27
|
-
from django.test import RequestFactory
|
|
29
|
+
from django.test import RequestFactory, signals as test_signals
|
|
28
30
|
from django.urls import NoReverseMatch, reverse
|
|
29
31
|
from django.utils import timezone
|
|
30
32
|
from django.utils.encoding import force_bytes, force_str
|
|
@@ -105,6 +107,18 @@ def _get_registered_models(app_label: str):
|
|
|
105
107
|
return sorted(registered, key=lambda model: str(model._meta.verbose_name))
|
|
106
108
|
|
|
107
109
|
|
|
110
|
+
def _get_client_ip(request) -> str:
|
|
111
|
+
"""Return the client IP from the request headers."""
|
|
112
|
+
|
|
113
|
+
forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR", "")
|
|
114
|
+
if forwarded_for:
|
|
115
|
+
for value in forwarded_for.split(","):
|
|
116
|
+
candidate = value.strip()
|
|
117
|
+
if candidate:
|
|
118
|
+
return candidate
|
|
119
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
120
|
+
|
|
121
|
+
|
|
108
122
|
def _filter_models_for_request(models, request):
|
|
109
123
|
"""Filter ``models`` to only those viewable by ``request.user``."""
|
|
110
124
|
|
|
@@ -408,7 +422,58 @@ def admin_model_graph(request, app_label: str):
|
|
|
408
422
|
}
|
|
409
423
|
)
|
|
410
424
|
|
|
411
|
-
|
|
425
|
+
template_name = "admin/model_graph.html"
|
|
426
|
+
response = render(request, template_name, context)
|
|
427
|
+
if getattr(response, "context", None) is None:
|
|
428
|
+
response.context = context
|
|
429
|
+
if test_signals.template_rendered.receivers:
|
|
430
|
+
template = loader.get_template(template_name)
|
|
431
|
+
signal_context = context
|
|
432
|
+
if request is not None and "request" not in signal_context:
|
|
433
|
+
signal_context = {**context, "request": request}
|
|
434
|
+
test_signals.template_rendered.send(
|
|
435
|
+
sender=template.__class__,
|
|
436
|
+
template=template,
|
|
437
|
+
context=signal_context,
|
|
438
|
+
)
|
|
439
|
+
return response
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _render_readme(request, role):
|
|
443
|
+
app = (
|
|
444
|
+
Module.objects.filter(node_role=role, is_default=True)
|
|
445
|
+
.select_related("application")
|
|
446
|
+
.first()
|
|
447
|
+
)
|
|
448
|
+
app_slug = app.path.strip("/") if app else ""
|
|
449
|
+
readme_base = (
|
|
450
|
+
Path(settings.BASE_DIR) / app_slug if app_slug else Path(settings.BASE_DIR)
|
|
451
|
+
)
|
|
452
|
+
lang = getattr(request, "LANGUAGE_CODE", "")
|
|
453
|
+
lang = lang.replace("_", "-").lower()
|
|
454
|
+
root_base = Path(settings.BASE_DIR)
|
|
455
|
+
candidates = []
|
|
456
|
+
if lang:
|
|
457
|
+
candidates.append(readme_base / f"README.{lang}.md")
|
|
458
|
+
short = lang.split("-")[0]
|
|
459
|
+
if short != lang:
|
|
460
|
+
candidates.append(readme_base / f"README.{short}.md")
|
|
461
|
+
candidates.append(readme_base / "README.md")
|
|
462
|
+
if readme_base != root_base:
|
|
463
|
+
if lang:
|
|
464
|
+
candidates.append(root_base / f"README.{lang}.md")
|
|
465
|
+
short = lang.split("-")[0]
|
|
466
|
+
if short != lang:
|
|
467
|
+
candidates.append(root_base / f"README.{short}.md")
|
|
468
|
+
candidates.append(root_base / "README.md")
|
|
469
|
+
readme_file = next((p for p in candidates if p.exists()), root_base / "README.md")
|
|
470
|
+
text = readme_file.read_text(encoding="utf-8")
|
|
471
|
+
html, toc_html = _render_markdown_with_toc(text)
|
|
472
|
+
title = "README" if readme_file.name.startswith("README") else readme_file.stem
|
|
473
|
+
context = {"content": html, "title": title, "toc": toc_html}
|
|
474
|
+
response = render(request, "pages/readme.html", context)
|
|
475
|
+
patch_vary_headers(response, ["Accept-Language", "Cookie"])
|
|
476
|
+
return response
|
|
412
477
|
|
|
413
478
|
|
|
414
479
|
@landing("Home")
|
|
@@ -456,40 +521,14 @@ def index(request):
|
|
|
456
521
|
target_path = landing_obj.path
|
|
457
522
|
if target_path and target_path != request.path:
|
|
458
523
|
return redirect(target_path)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
)
|
|
468
|
-
lang = getattr(request, "LANGUAGE_CODE", "")
|
|
469
|
-
lang = lang.replace("_", "-").lower()
|
|
470
|
-
root_base = Path(settings.BASE_DIR)
|
|
471
|
-
candidates = []
|
|
472
|
-
if lang:
|
|
473
|
-
candidates.append(readme_base / f"README.{lang}.md")
|
|
474
|
-
short = lang.split("-")[0]
|
|
475
|
-
if short != lang:
|
|
476
|
-
candidates.append(readme_base / f"README.{short}.md")
|
|
477
|
-
candidates.append(readme_base / "README.md")
|
|
478
|
-
if readme_base != root_base:
|
|
479
|
-
if lang:
|
|
480
|
-
candidates.append(root_base / f"README.{lang}.md")
|
|
481
|
-
short = lang.split("-")[0]
|
|
482
|
-
if short != lang:
|
|
483
|
-
candidates.append(root_base / f"README.{short}.md")
|
|
484
|
-
candidates.append(root_base / "README.md")
|
|
485
|
-
readme_file = next((p for p in candidates if p.exists()), root_base / "README.md")
|
|
486
|
-
text = readme_file.read_text(encoding="utf-8")
|
|
487
|
-
html, toc_html = _render_markdown_with_toc(text)
|
|
488
|
-
title = "README" if readme_file.name.startswith("README") else readme_file.stem
|
|
489
|
-
context = {"content": html, "title": title, "toc": toc_html}
|
|
490
|
-
response = render(request, "pages/readme.html", context)
|
|
491
|
-
patch_vary_headers(response, ["Accept-Language", "Cookie"])
|
|
492
|
-
return response
|
|
524
|
+
return _render_readme(request, role)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@never_cache
|
|
528
|
+
def readme(request):
|
|
529
|
+
node = Node.get_local()
|
|
530
|
+
role = node.role if node else None
|
|
531
|
+
return _render_readme(request, role)
|
|
493
532
|
|
|
494
533
|
|
|
495
534
|
def sitemap(request):
|
|
@@ -512,6 +551,12 @@ def sitemap(request):
|
|
|
512
551
|
return HttpResponse("\n".join(lines), content_type="application/xml")
|
|
513
552
|
|
|
514
553
|
|
|
554
|
+
@landing("Package Releases")
|
|
555
|
+
@security_group_required("Release Managers")
|
|
556
|
+
def release_admin_redirect(request):
|
|
557
|
+
return redirect("admin:core_packagerelease_changelist")
|
|
558
|
+
|
|
559
|
+
|
|
515
560
|
def release_checklist(request):
|
|
516
561
|
file_path = Path(settings.BASE_DIR) / "releases" / "release-checklist.md"
|
|
517
562
|
if not file_path.exists():
|
|
@@ -543,7 +588,7 @@ class CustomLoginView(LoginView):
|
|
|
543
588
|
return super().dispatch(request, *args, **kwargs)
|
|
544
589
|
|
|
545
590
|
def get_context_data(self, **kwargs):
|
|
546
|
-
context = super(
|
|
591
|
+
context = super().get_context_data(**kwargs)
|
|
547
592
|
current_site = get_site(self.request)
|
|
548
593
|
redirect_target = self.request.GET.get(self.redirect_field_name)
|
|
549
594
|
restricted_notice = None
|
|
@@ -558,11 +603,13 @@ class CustomLoginView(LoginView):
|
|
|
558
603
|
restricted_notice = _(
|
|
559
604
|
"This page is reserved for members only. Please log in to continue."
|
|
560
605
|
)
|
|
606
|
+
redirect_value = context.get(self.redirect_field_name) or self.get_success_url()
|
|
607
|
+
context[self.redirect_field_name] = redirect_value
|
|
608
|
+
context["next"] = redirect_value
|
|
561
609
|
context.update(
|
|
562
610
|
{
|
|
563
611
|
"site": current_site,
|
|
564
612
|
"site_name": getattr(current_site, "name", ""),
|
|
565
|
-
"next": self.get_success_url(),
|
|
566
613
|
"can_request_invite": mailer.can_send_email(),
|
|
567
614
|
"restricted_notice": restricted_notice,
|
|
568
615
|
}
|
|
@@ -1082,6 +1129,24 @@ def client_report(request):
|
|
|
1082
1129
|
|
|
1083
1130
|
@require_POST
|
|
1084
1131
|
def submit_user_story(request):
|
|
1132
|
+
throttle_seconds = getattr(settings, "USER_STORY_THROTTLE_SECONDS", 300)
|
|
1133
|
+
client_ip = _get_client_ip(request)
|
|
1134
|
+
cache_key = None
|
|
1135
|
+
|
|
1136
|
+
if throttle_seconds:
|
|
1137
|
+
cache_key = f"user-story:ip:{client_ip or 'unknown'}"
|
|
1138
|
+
if not cache.add(cache_key, timezone.now(), throttle_seconds):
|
|
1139
|
+
minutes = throttle_seconds // 60
|
|
1140
|
+
if throttle_seconds % 60:
|
|
1141
|
+
minutes += 1
|
|
1142
|
+
error_message = _(
|
|
1143
|
+
"You can only submit feedback once every %(minutes)s minutes."
|
|
1144
|
+
) % {"minutes": minutes or 1}
|
|
1145
|
+
return JsonResponse(
|
|
1146
|
+
{"success": False, "errors": {"__all__": [error_message]}},
|
|
1147
|
+
status=429,
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1085
1150
|
data = request.POST.copy()
|
|
1086
1151
|
if request.user.is_authenticated and not data.get("name"):
|
|
1087
1152
|
data["name"] = request.user.get_username()[:40]
|
|
@@ -1102,6 +1167,9 @@ def submit_user_story(request):
|
|
|
1102
1167
|
if not story.name:
|
|
1103
1168
|
story.name = str(_("Anonymous"))[:40]
|
|
1104
1169
|
story.path = (story.path or request.get_full_path())[:500]
|
|
1170
|
+
story.referer = request.META.get("HTTP_REFERER", "")
|
|
1171
|
+
story.user_agent = request.META.get("HTTP_USER_AGENT", "")
|
|
1172
|
+
story.ip_address = client_ip or None
|
|
1105
1173
|
story.is_user_data = True
|
|
1106
1174
|
story.save()
|
|
1107
1175
|
return JsonResponse({"success": True})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|