arthexis 0.1.3__tar.gz → 0.1.5__tar.gz

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.

Files changed (125) hide show
  1. {arthexis-0.1.3 → arthexis-0.1.5}/PKG-INFO +2 -2
  2. {arthexis-0.1.3 → arthexis-0.1.5}/README.md +1 -1
  3. {arthexis-0.1.3 → arthexis-0.1.5}/arthexis.egg-info/PKG-INFO +2 -2
  4. {arthexis-0.1.3 → arthexis-0.1.5}/core/admin.py +23 -9
  5. {arthexis-0.1.3 → arthexis-0.1.5}/core/models.py +4 -3
  6. {arthexis-0.1.3 → arthexis-0.1.5}/core/views.py +14 -1
  7. {arthexis-0.1.3 → arthexis-0.1.5}/pages/tests.py +23 -0
  8. {arthexis-0.1.3 → arthexis-0.1.5}/pages/views.py +2 -1
  9. {arthexis-0.1.3 → arthexis-0.1.5}/pyproject.toml +1 -1
  10. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_package_release_admin_actions.py +19 -6
  11. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_release_fixture_cleanup.py +5 -0
  12. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_release_progress.py +52 -1
  13. {arthexis-0.1.3 → arthexis-0.1.5}/LICENSE +0 -0
  14. {arthexis-0.1.3 → arthexis-0.1.5}/arthexis.egg-info/SOURCES.txt +0 -0
  15. {arthexis-0.1.3 → arthexis-0.1.5}/arthexis.egg-info/dependency_links.txt +0 -0
  16. {arthexis-0.1.3 → arthexis-0.1.5}/arthexis.egg-info/requires.txt +0 -0
  17. {arthexis-0.1.3 → arthexis-0.1.5}/arthexis.egg-info/top_level.txt +0 -0
  18. {arthexis-0.1.3 → arthexis-0.1.5}/config/__init__.py +0 -0
  19. {arthexis-0.1.3 → arthexis-0.1.5}/config/active_app.py +0 -0
  20. {arthexis-0.1.3 → arthexis-0.1.5}/config/asgi.py +0 -0
  21. {arthexis-0.1.3 → arthexis-0.1.5}/config/auth_app.py +0 -0
  22. {arthexis-0.1.3 → arthexis-0.1.5}/config/celery.py +0 -0
  23. {arthexis-0.1.3 → arthexis-0.1.5}/config/context_processors.py +0 -0
  24. {arthexis-0.1.3 → arthexis-0.1.5}/config/loadenv.py +0 -0
  25. {arthexis-0.1.3 → arthexis-0.1.5}/config/logging.py +0 -0
  26. {arthexis-0.1.3 → arthexis-0.1.5}/config/middleware.py +0 -0
  27. {arthexis-0.1.3 → arthexis-0.1.5}/config/offline.py +0 -0
  28. {arthexis-0.1.3 → arthexis-0.1.5}/config/settings.py +0 -0
  29. {arthexis-0.1.3 → arthexis-0.1.5}/config/urls.py +0 -0
  30. {arthexis-0.1.3 → arthexis-0.1.5}/config/wsgi.py +0 -0
  31. {arthexis-0.1.3 → arthexis-0.1.5}/core/__init__.py +0 -0
  32. {arthexis-0.1.3 → arthexis-0.1.5}/core/apps.py +0 -0
  33. {arthexis-0.1.3 → arthexis-0.1.5}/core/backends.py +0 -0
  34. {arthexis-0.1.3 → arthexis-0.1.5}/core/entity.py +0 -0
  35. {arthexis-0.1.3 → arthexis-0.1.5}/core/environment.py +0 -0
  36. {arthexis-0.1.3 → arthexis-0.1.5}/core/fields.py +0 -0
  37. {arthexis-0.1.3 → arthexis-0.1.5}/core/lcd_screen.py +0 -0
  38. {arthexis-0.1.3 → arthexis-0.1.5}/core/middleware.py +0 -0
  39. {arthexis-0.1.3 → arthexis-0.1.5}/core/notifications.py +0 -0
  40. {arthexis-0.1.3 → arthexis-0.1.5}/core/release.py +0 -0
  41. {arthexis-0.1.3 → arthexis-0.1.5}/core/system.py +0 -0
  42. {arthexis-0.1.3 → arthexis-0.1.5}/core/tasks.py +0 -0
  43. {arthexis-0.1.3 → arthexis-0.1.5}/core/tests.py +0 -0
  44. {arthexis-0.1.3 → arthexis-0.1.5}/core/urls.py +0 -0
  45. {arthexis-0.1.3 → arthexis-0.1.5}/core/user_data.py +0 -0
  46. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/__init__.py +0 -0
  47. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/actions.py +0 -0
  48. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/admin.py +0 -0
  49. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/apps.py +0 -0
  50. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/lcd.py +0 -0
  51. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/models.py +0 -0
  52. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/tasks.py +0 -0
  53. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/tests.py +0 -0
  54. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/urls.py +0 -0
  55. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/utils.py +0 -0
  56. {arthexis-0.1.3 → arthexis-0.1.5}/nodes/views.py +0 -0
  57. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/__init__.py +0 -0
  58. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/admin.py +0 -0
  59. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/apps.py +0 -0
  60. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/consumers.py +0 -0
  61. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/evcs.py +0 -0
  62. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/models.py +0 -0
  63. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/routing.py +0 -0
  64. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/simulator.py +0 -0
  65. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/store.py +0 -0
  66. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/tasks.py +0 -0
  67. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/test_export_import.py +0 -0
  68. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/test_rfid.py +0 -0
  69. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/tests.py +0 -0
  70. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/transactions_io.py +0 -0
  71. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/urls.py +0 -0
  72. {arthexis-0.1.3 → arthexis-0.1.5}/ocpp/views.py +0 -0
  73. {arthexis-0.1.3 → arthexis-0.1.5}/pages/__init__.py +0 -0
  74. {arthexis-0.1.3 → arthexis-0.1.5}/pages/admin.py +0 -0
  75. {arthexis-0.1.3 → arthexis-0.1.5}/pages/apps.py +0 -0
  76. {arthexis-0.1.3 → arthexis-0.1.5}/pages/checks.py +0 -0
  77. {arthexis-0.1.3 → arthexis-0.1.5}/pages/context_processors.py +0 -0
  78. {arthexis-0.1.3 → arthexis-0.1.5}/pages/models.py +0 -0
  79. {arthexis-0.1.3 → arthexis-0.1.5}/pages/urls.py +0 -0
  80. {arthexis-0.1.3 → arthexis-0.1.5}/pages/utils.py +0 -0
  81. {arthexis-0.1.3 → arthexis-0.1.5}/setup.cfg +0 -0
  82. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_acronym_capitalization.py +0 -0
  83. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_admin_history.py +0 -0
  84. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_admin_index_actions.py +0 -0
  85. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_awg_admin.py +0 -0
  86. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_csrf_failure.py +0 -0
  87. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_csrf_origin_subnet.py +0 -0
  88. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_email_collector.py +0 -0
  89. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_email_inbox.py +0 -0
  90. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_email_inbox_admin.py +0 -0
  91. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_email_inbox_search_action.py +0 -0
  92. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_env_refresh_clean.py +0 -0
  93. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_env_refresh_unlink.py +0 -0
  94. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_footer_admin_link.py +0 -0
  95. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_footer_no_references.py +0 -0
  96. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_footer_presence.py +0 -0
  97. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_footer_render.py +0 -0
  98. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_github_token.py +0 -0
  99. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_install_script.py +0 -0
  100. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_language_switch.py +0 -0
  101. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_lcd_smbus2.py +0 -0
  102. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_localhost_admin_backend.py +0 -0
  103. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_model_verbose_name_capitalization.py +0 -0
  104. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_notifications_fallback.py +0 -0
  105. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_notify_command.py +0 -0
  106. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_odoo_profile.py +0 -0
  107. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_odoo_profile_admin.py +0 -0
  108. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_offline.py +0 -0
  109. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_pypi_token.py +0 -0
  110. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_readme_language.py +0 -0
  111. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_reference_qr_code.py +0 -0
  112. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_reference_transaction_uuid.py +0 -0
  113. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_register_site_apps_command.py +0 -0
  114. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_release_logs.py +0 -0
  115. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_release_mapping.py +0 -0
  116. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_release_tasks.py +0 -0
  117. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_rfid_admin_reference_clear.py +0 -0
  118. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_rfid_admin_scan_csrf.py +0 -0
  119. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_rfid_background_reader.py +0 -0
  120. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_save_as_copy.py +0 -0
  121. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_seed_data.py +0 -0
  122. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_sigil_resolution.py +0 -0
  123. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_urls_autodiscover.py +0 -0
  124. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_user_datum_admin.py +0 -0
  125. {arthexis-0.1.3 → arthexis-0.1.5}/tests/test_vscode_manage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Django-based MESH system
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: MIT
@@ -118,7 +118,7 @@ Arthexis Constellation is a narrative-driven Django-based suite that centralizes
118
118
  - Single codebase with special features per role
119
119
 
120
120
  ## Support
121
- You may contact us at tecnologia at gelectriic dot com or visit our web page https://www.gelectriic.com/ for professional services and commercial support.
121
+ You may contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
122
122
 
123
123
  ## About Me
124
124
  > "What? You want to know about me too? Well, I enjoy developing software, roleplaying games, long walks on the beach and a fourth secret thing."
@@ -10,7 +10,7 @@ Arthexis Constellation is a narrative-driven Django-based suite that centralizes
10
10
  - Single codebase with special features per role
11
11
 
12
12
  ## Support
13
- You may contact us at tecnologia at gelectriic dot com or visit our web page https://www.gelectriic.com/ for professional services and commercial support.
13
+ You may contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
14
14
 
15
15
  ## About Me
16
16
  > "What? You want to know about me too? Well, I enjoy developing software, roleplaying games, long walks on the beach and a fourth secret thing."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Django-based MESH system
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: MIT
@@ -118,7 +118,7 @@ Arthexis Constellation is a narrative-driven Django-based suite that centralizes
118
118
  - Single codebase with special features per role
119
119
 
120
120
  ## Support
121
- You may contact us at tecnologia at gelectriic dot com or visit our web page https://www.gelectriic.com/ for professional services and commercial support.
121
+ You may contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
122
122
 
123
123
  ## About Me
124
124
  > "What? You want to know about me too? Well, I enjoy developing software, roleplaying games, long walks on the beach and a fourth secret thing."
@@ -3,7 +3,7 @@ from django.contrib import admin
3
3
  from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
4
4
  from django.urls import path, reverse
5
5
  from django.shortcuts import redirect, render
6
- from django.http import JsonResponse
6
+ from django.http import JsonResponse, HttpResponseBase
7
7
  from django.template.response import TemplateResponse
8
8
  from django.views.decorators.csrf import csrf_exempt
9
9
  from django.core.exceptions import ValidationError
@@ -17,6 +17,7 @@ from import_export import resources, fields
17
17
  from import_export.admin import ImportExportModelAdmin
18
18
  from import_export.widgets import ForeignKeyWidget
19
19
  from django.contrib.auth.models import Group
20
+ from django.templatetags.static import static
20
21
  from django.utils.html import format_html
21
22
  import json
22
23
  import uuid
@@ -49,6 +50,19 @@ from .user_data import UserDatumAdminMixin
49
50
  admin.site.unregister(Group)
50
51
 
51
52
 
53
+ class SaveBeforeChangeAction(DjangoObjectActions):
54
+ def response_change(self, request, obj):
55
+ action = request.POST.get("_action")
56
+ if action:
57
+ allowed = self.get_change_actions(request, str(obj.pk), None)
58
+ if action in allowed and hasattr(self, action):
59
+ response = getattr(self, action)(request, obj)
60
+ if isinstance(response, HttpResponseBase):
61
+ return response
62
+ return redirect(request.path)
63
+ return super().response_change(request, obj)
64
+
65
+
52
66
  @admin.register(Reference)
53
67
  class ReferenceAdmin(admin.ModelAdmin):
54
68
  list_display = (
@@ -131,7 +145,7 @@ class ReleaseManagerAdmin(admin.ModelAdmin):
131
145
 
132
146
 
133
147
  @admin.register(Package)
134
- class PackageAdmin(DjangoObjectActions, admin.ModelAdmin):
148
+ class PackageAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
135
149
  list_display = ("name", "description", "homepage_url", "release_manager")
136
150
  actions = ["prepare_next_release"]
137
151
  change_actions = ["prepare_next_release_action"]
@@ -755,7 +769,7 @@ class RFIDAdmin(ImportExportModelAdmin):
755
769
 
756
770
 
757
771
  @admin.register(PackageRelease)
758
- class PackageReleaseAdmin(DjangoObjectActions, admin.ModelAdmin):
772
+ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
759
773
  list_display = (
760
774
  "version",
761
775
  "package",
@@ -808,18 +822,18 @@ class PackageReleaseAdmin(DjangoObjectActions, admin.ModelAdmin):
808
822
  publish_release_action.short_description = "Publish this release"
809
823
 
810
824
  @staticmethod
811
- def _checkbox(value: bool) -> str:
812
- return format_html(
813
- '<input type="checkbox"{} disabled>', " checked" if value else ""
814
- )
825
+ def _boolean_icon(value: bool) -> str:
826
+ icon = static("admin/img/icon-yes.svg" if value else "admin/img/icon-no.svg")
827
+ alt = "True" if value else "False"
828
+ return format_html('<img src="{}" alt="{}">', icon, alt)
815
829
 
816
830
  @admin.display(description="Published")
817
831
  def published_status(self, obj):
818
- return self._checkbox(obj.is_published)
832
+ return self._boolean_icon(obj.is_published)
819
833
 
820
834
  @admin.display(description="Is current")
821
835
  def is_current(self, obj):
822
- return self._checkbox(obj.is_current)
836
+ return self._boolean_icon(obj.is_current)
823
837
 
824
838
  def pr_link(self, obj):
825
839
  if obj.pr_url:
@@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy as _
10
10
  from django.core.validators import RegexValidator
11
11
  from django.core.exceptions import ValidationError
12
12
  from django.apps import apps
13
- from django.db.models.signals import m2m_changed, post_delete
13
+ from django.db.models.signals import m2m_changed, post_delete, post_save
14
14
  from django.dispatch import receiver
15
15
  from datetime import timedelta
16
16
  from django.contrib.contenttypes.models import ContentType
@@ -1257,8 +1257,9 @@ class PackageRelease(Entity):
1257
1257
  return self.revision[-6:] if self.revision else ""
1258
1258
 
1259
1259
 
1260
- @receiver(post_delete, sender=PackageRelease)
1261
- def _delete_release_fixture(sender, instance, **kwargs) -> None:
1260
+ @receiver([post_save, post_delete], sender=PackageRelease)
1261
+ def _update_release_fixture(sender, instance, **kwargs) -> None:
1262
+ """Keep the release fixture in sync with the database."""
1262
1263
  PackageRelease.dump_fixture()
1263
1264
 
1264
1265
  # Ensure each RFID can only be linked to one energy account
@@ -43,6 +43,16 @@ def _changelog_notes(version: str) -> str:
43
43
 
44
44
  def _step_check_pypi(release, ctx, log_path: Path) -> None:
45
45
  from . import release as release_utils
46
+ from packaging.version import Version
47
+
48
+ version_path = Path("VERSION")
49
+ if version_path.exists():
50
+ current = version_path.read_text(encoding="utf-8").strip()
51
+ if current and Version(release.version) < Version(current):
52
+ raise Exception(
53
+ f"Version {release.version} is older than existing {current}"
54
+ )
55
+ version_path.write_text(release.version + "\n", encoding="utf-8")
46
56
 
47
57
  _append_log(log_path, f"Checking if version {release.version} exists on PyPI")
48
58
  if release_utils.network_available():
@@ -389,7 +399,10 @@ def release_progress(request, pk: int, action: str):
389
399
  identifier = f"{release.package.name}-{release.version}"
390
400
  if release.revision:
391
401
  identifier = f"{identifier}-{release.revision[:7]}"
392
- log_name = ctx.get("log") or f"{identifier}.log"
402
+ log_name = f"{identifier}.log"
403
+ if ctx.get("log") != log_name:
404
+ ctx = {"step": 0, "log": log_name}
405
+ step_count = 0
393
406
  log_path = Path("logs") / log_name
394
407
  ctx.setdefault("log", log_name)
395
408
 
@@ -9,6 +9,7 @@ from pages.models import Application, Module, SiteBadge, Favorite
9
9
  from core.user_data import UserDatum
10
10
  from pages.admin import ApplicationAdmin
11
11
  from django.apps import apps as django_apps
12
+ from core.models import AdminHistory
12
13
  from django.core.files.uploadedfile import SimpleUploadedFile
13
14
  import base64
14
15
  import tempfile
@@ -86,6 +87,13 @@ class InvitationTests(TestCase):
86
87
  resp = self.client.get(reverse("pages:request-invite"))
87
88
  self.assertIn("csrftoken", resp.cookies)
88
89
 
90
+ def test_request_invite_allows_post_without_csrf(self):
91
+ client = Client(enforce_csrf_checks=True)
92
+ resp = client.post(
93
+ reverse("pages:request-invite"), {"email": "invite@example.com"}
94
+ )
95
+ self.assertEqual(resp.status_code, 200)
96
+
89
97
  def test_invitation_flow(self):
90
98
  resp = self.client.post(
91
99
  reverse("pages:request-invite"), {"email": "invite@example.com"}
@@ -626,3 +634,18 @@ class FavoriteTests(TestCase):
626
634
  resp = self.client.get(reverse("admin:index"))
627
635
  self.assertContains(resp, reverse("admin:pages_application_changelist"))
628
636
  self.assertContains(resp, reverse("admin:nodes_noderole_changelist"))
637
+
638
+ def test_dashboard_merges_duplicate_future_actions(self):
639
+ ct = ContentType.objects.get_for_model(NodeRole)
640
+ Favorite.objects.create(user=self.user, content_type=ct)
641
+ role = NodeRole.objects.create(name="DataRole2")
642
+ UserDatum.objects.create(user=self.user, content_type=ct, object_id=role.pk)
643
+ AdminHistory.objects.create(
644
+ user=self.user,
645
+ content_type=ct,
646
+ url=reverse("admin:nodes_noderole_changelist"),
647
+ )
648
+ resp = self.client.get(reverse("admin:index"))
649
+ url = reverse("admin:nodes_noderole_changelist")
650
+ self.assertEqual(resp.content.decode().count(url), 1)
651
+ self.assertContains(resp, NodeRole._meta.verbose_name_plural)
@@ -16,7 +16,7 @@ from django.utils.encoding import force_bytes, force_str
16
16
  from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
17
17
  from django.core.mail import send_mail
18
18
  from django.utils.translation import gettext as _
19
- from django.views.decorators.csrf import ensure_csrf_cookie
19
+ from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
20
20
 
21
21
  import markdown
22
22
  from pages.utils import landing
@@ -116,6 +116,7 @@ login_view = CustomLoginView.as_view()
116
116
  class InvitationRequestForm(forms.Form):
117
117
  email = forms.EmailField()
118
118
 
119
+ @csrf_exempt
119
120
  @ensure_csrf_cookie
120
121
  def request_invite(request):
121
122
  form = InvitationRequestForm(request.POST if request.method == "POST" else None)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "arthexis"
7
- version = "0.1.3"
7
+ version = "0.1.5"
8
8
  description = "Django-based MESH system"
9
9
  requires-python = ">=3.10"
10
10
  license = "MIT"
@@ -28,12 +28,10 @@ class PackageReleaseAdminActionsTests(TestCase):
28
28
 
29
29
  def test_change_page_contains_publish_action(self):
30
30
  change_url = reverse("admin:core_packagerelease_change", args=[self.release.pk])
31
- action_url = reverse(
32
- "admin:core_packagerelease_actions",
33
- args=[self.release.pk, "publish_release_action"],
34
- )
35
31
  resp = self.client.get(change_url)
36
- self.assertContains(resp, action_url)
32
+ self.assertContains(
33
+ resp, 'name="_action" value="publish_release_action"'
34
+ )
37
35
 
38
36
  def test_publish_action_redirects(self):
39
37
  url = reverse(
@@ -45,6 +43,21 @@ class PackageReleaseAdminActionsTests(TestCase):
45
43
  resp, reverse("release-progress", args=[self.release.pk, "publish"])
46
44
  )
47
45
 
46
+ def test_publish_action_saves_changes_before_execution(self):
47
+ change_url = reverse("admin:core_packagerelease_change", args=[self.release.pk])
48
+ data = {
49
+ "package": self.package.pk,
50
+ "release_manager": "",
51
+ "version": "1.0.1",
52
+ "_action": "publish_release_action",
53
+ }
54
+ resp = self.client.post(change_url, data)
55
+ self.assertRedirects(
56
+ resp, reverse("release-progress", args=[self.release.pk, "publish"])
57
+ )
58
+ self.release.refresh_from_db()
59
+ self.assertEqual(self.release.version, "1.0.1")
60
+
48
61
  def test_change_page_pypi_url_readonly(self):
49
62
  change_url = reverse("admin:core_packagerelease_change", args=[self.release.pk])
50
63
  resp = self.client.get(change_url)
@@ -77,7 +90,7 @@ class PackageReleaseAdminActionsTests(TestCase):
77
90
  resp = self.client.get(list_url)
78
91
  content = resp.content.decode()
79
92
  self.assertIn("Is current", content)
80
- self.assertIn('<input type="checkbox" checked disabled>', content)
93
+ self.assertIn('icon-yes.svg', content)
81
94
 
82
95
  def test_release_revision_defaults_to_repo_revision(self):
83
96
  expected = revision_utils.get_revision()
@@ -30,3 +30,8 @@ class ReleaseFixtureCleanupTests(TestCase):
30
30
  data = self.fixture_path.read_text()
31
31
  self.assertNotIn("1.0.0", data)
32
32
 
33
+ def test_create_updates_fixture(self):
34
+ PackageRelease.objects.create(package=self.package, version="2.0.0")
35
+ data = self.fixture_path.read_text()
36
+ self.assertIn("2.0.0", data)
37
+
@@ -62,7 +62,12 @@ class ReleaseProgressTests(TestCase):
62
62
  self.assertContains(resp, "All steps completed")
63
63
  self.assertContains(
64
64
  resp,
65
- '<a href="http://example.com/pr/1" target="_blank">http://example.com/pr/1</a>',
65
+ '<a href="http://example.com/pr/1" target="_blank" rel="noopener">http://example.com/pr/1</a>',
66
+ html=True,
67
+ )
68
+ self.assertContains(
69
+ resp,
70
+ '<a href="https://pypi.org/project/pkg/1.0.0/" target="_blank" rel="noopener">https://pypi.org/project/pkg/1.0.0/</a>',
66
71
  html=True,
67
72
  )
68
73
  release.refresh_from_db()
@@ -94,6 +99,11 @@ class ReleaseProgressTests(TestCase):
94
99
  self.assertEqual(resp.status_code, 200)
95
100
  self.assertContains(resp, "All steps completed")
96
101
  self.assertIsNone(resp.context["pr_url"])
102
+ self.assertContains(
103
+ resp,
104
+ '<a href="https://pypi.org/project/pkg/1.1.0/" target="_blank" rel="noopener">https://pypi.org/project/pkg/1.1.0/</a>',
105
+ html=True,
106
+ )
97
107
  release.refresh_from_db()
98
108
  self.assertTrue(release.is_published)
99
109
  pub.assert_called_once()
@@ -112,3 +122,44 @@ class ReleaseProgressTests(TestCase):
112
122
  self.assertContains(resp, f'<a href="{app_url}">Business Models</a>')
113
123
  list_url = reverse("admin:core_packagerelease_changelist")
114
124
  self.assertContains(resp, f'<a href="{list_url}">Package Releases</a>')
125
+
126
+ def test_check_step_writes_version_file(self):
127
+ release = PackageRelease.objects.create(package=self.package, version="2.0.0")
128
+ url = reverse("release-progress", args=[release.pk, "publish"])
129
+ self.client.get(url)
130
+ with patch("core.views.release_utils.network_available", return_value=False):
131
+ resp = self.client.get(f"{url}?step=0")
132
+ self.assertEqual(resp.status_code, 200)
133
+ self.assertEqual(self.version_path.read_text().strip(), "2.0.0")
134
+
135
+ def test_check_step_fails_when_version_goes_backwards(self):
136
+ self.version_path.write_text("3.0.0\n")
137
+ release = PackageRelease.objects.create(package=self.package, version="2.0.0")
138
+ url = reverse("release-progress", args=[release.pk, "publish"])
139
+ self.client.get(url)
140
+ with patch("core.views.release_utils.network_available", return_value=False):
141
+ resp = self.client.get(f"{url}?step=0")
142
+ self.assertEqual(resp.context["error"], "Version 2.0.0 is older than existing 3.0.0")
143
+ self.assertEqual(self.version_path.read_text().strip(), "3.0.0")
144
+
145
+ def test_session_resets_when_version_changes(self):
146
+ release = PackageRelease.objects.create(package=self.package, version="1.2.0")
147
+ url = reverse("release-progress", args=[release.pk, "publish"])
148
+ self.client.get(url)
149
+ with patch("core.views.release_utils.network_available", return_value=False):
150
+ self.client.get(f"{url}?step=0")
151
+
152
+ release.version = "1.3.0"
153
+ release.save()
154
+
155
+ resp = self.client.get(url)
156
+ expected_log = f"logs/pkg-1.3.0-{release.revision[:7]}.log"
157
+ self.assertEqual(resp.context["log_path"], expected_log)
158
+ self.assertEqual(resp.context["current_step"], 0)
159
+
160
+ with patch("core.views.release_utils.network_available", return_value=False):
161
+ self.client.get(f"{url}?step=0")
162
+
163
+ log_content = Path(expected_log).read_text()
164
+ self.assertIn("Checking if version 1.3.0 exists on PyPI", log_content)
165
+ self.assertNotIn("1.2.0", log_content)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes