refactor: automate street synchronization and spatial inheritance for sign poles and panels
This commit is contained in:
parent
2c207fcfbb
commit
48d7796e45
4 changed files with 429 additions and 49 deletions
|
|
@ -25,7 +25,9 @@ class SignPoleForm(forms.ModelForm):
|
|||
lat = self.cleaned_data.get('lat')
|
||||
lon = self.cleaned_data.get('lon')
|
||||
if lat and lon:
|
||||
instance.geom = Point(lon, lat, srid=4326)
|
||||
instance.lon = float(lon)
|
||||
instance.lat = float(lat)
|
||||
instance.geom = Point(float(lon), float(lat), srid=4326)
|
||||
instance.geojson = instance.geom.geojson
|
||||
instance.geom.transform(3812)
|
||||
if commit:
|
||||
|
|
@ -59,7 +61,11 @@ class SignPanelForm(forms.ModelForm):
|
|||
lat = self.cleaned_data.get('lat')
|
||||
lon = self.cleaned_data.get('lon')
|
||||
if lat and lon:
|
||||
instance.geom = Point(lon, lat, srid=4326)
|
||||
instance.lon = float(lon)
|
||||
instance.lat = float(lat)
|
||||
instance.geom = Point(float(lon), float(lat), srid=4326)
|
||||
instance.geojson = instance.geom.geojson
|
||||
instance.geom.transform(3812)
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
|
|
|||
|
|
@ -70,8 +70,19 @@ class SignStreet(AbstractLocation):
|
|||
street_level = models.CharField(max_length=50, null=True, blank=True)
|
||||
|
||||
def get_active_assets(self):
|
||||
from django.db.models import Q
|
||||
if hasattr(self, 'signpoles'):
|
||||
signpoles = self.signpoles.filter(status="active")
|
||||
signpanels = self.signpanels.filter(status="active")
|
||||
else:
|
||||
poles_qs = SignPole.objects.filter(status="active")
|
||||
panels_qs = SignPanel.objects.filter(status="active")
|
||||
if self.geom:
|
||||
signpoles = poles_qs.filter(Q(geom__intersects=self.geom) | Q(street__name_fr__iexact=self.name_fr)).distinct()
|
||||
signpanels = panels_qs.filter(Q(geom__intersects=self.geom) | Q(street__name_fr__iexact=self.name_fr)).distinct()
|
||||
else:
|
||||
signpoles = poles_qs.filter(street__name_fr__iexact=self.name_fr)
|
||||
signpanels = panels_qs.filter(street__name_fr__iexact=self.name_fr)
|
||||
return {"signpoles": signpoles, "signpanels": signpanels}
|
||||
|
||||
def get_absolute_url(self):
|
||||
|
|
@ -153,6 +164,23 @@ class SignPole(AbstractGeoAsset):
|
|||
class Meta:
|
||||
indexes = [models.Index(fields=["code"])]
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
if self.geom and hasattr(self, 'sign_app_panels'):
|
||||
for panel in self.sign_app_panels.all():
|
||||
needs_save = False
|
||||
if panel.geom != self.geom or panel.lon != self.lon or panel.lat != self.lat:
|
||||
panel.geom = self.geom
|
||||
panel.lon = self.lon
|
||||
panel.lat = self.lat
|
||||
panel.geojson = self.geojson
|
||||
needs_save = True
|
||||
if panel.street_id != self.street_id and self.street_id:
|
||||
panel.street = self.street
|
||||
needs_save = True
|
||||
if needs_save:
|
||||
panel.save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("sign:sign_assets_detail", kwargs={"asset_model": "signpole", "asset_id": self.id})
|
||||
|
||||
|
|
@ -239,6 +267,26 @@ class SignPanel(AbstractGeoAsset):
|
|||
class Meta:
|
||||
indexes = [models.Index(fields=["code"])]
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.pole and getattr(self.pole, 'geom', None):
|
||||
self.geom = self.pole.geom
|
||||
self.lon = self.pole.lon
|
||||
self.lat = self.pole.lat
|
||||
self.geojson = self.pole.geojson
|
||||
if self.pole and self.pole.street and not self.street_id:
|
||||
self.street = self.pole.street
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pole and getattr(self.pole, 'geom', None):
|
||||
self.geom = self.pole.geom
|
||||
self.lon = self.pole.lon
|
||||
self.lat = self.pole.lat
|
||||
self.geojson = self.pole.geojson
|
||||
if self.pole and self.pole.street and not self.street_id:
|
||||
self.street = self.pole.street
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("sign:sign_assets_detail", kwargs={"asset_model": "signpanel", "asset_id": self.id})
|
||||
|
||||
|
|
|
|||
|
|
@ -784,3 +784,219 @@ class CopySignDataCommandTests(TestCase):
|
|||
self.assertEqual(SignPole.objects.filter(code="POL-001").count(), 1)
|
||||
self.assertEqual(SignPanel.objects.filter(code="PAN-001").count(), 1)
|
||||
|
||||
|
||||
class SignStreetAssignmentTests(TestCase):
|
||||
"""Tests for resolving AdrStreet and avoiding SignStreet assignment errors."""
|
||||
|
||||
def setUp(self):
|
||||
from django.contrib.gis.geos import Point, LineString, Polygon, MultiPolygon
|
||||
from common.models import Municipality, AdrStreet, AppView, UserConfig, Thematic, UserThematics
|
||||
from assets.models import AssetCategory
|
||||
from sign.models import (
|
||||
SignPoleModel,
|
||||
SignPanelModel,
|
||||
SignPanelType,
|
||||
SignStreet,
|
||||
Organization,
|
||||
)
|
||||
|
||||
self.client = Client()
|
||||
|
||||
# Create sign view and thematic
|
||||
self.sign_view, _ = AppView.objects.get_or_create(
|
||||
code='sign',
|
||||
defaults={
|
||||
'name_fr': 'Signalisation',
|
||||
'name_nl': 'Signalisatie',
|
||||
'url_name': 'sign:index',
|
||||
'order': 2,
|
||||
}
|
||||
)
|
||||
self.sign_thematic, _ = Thematic.objects.get_or_create(
|
||||
code='sign',
|
||||
defaults={
|
||||
'name_fr': 'Signalisation',
|
||||
'name_nl': 'Signalisatie',
|
||||
'color': '#0022ff',
|
||||
}
|
||||
)
|
||||
|
||||
# Admin user with full edit access
|
||||
self.user = User.objects.create_superuser(
|
||||
username='admin_sign',
|
||||
password='testpass123',
|
||||
email='admin@test.com'
|
||||
)
|
||||
self.config = UserConfig.objects.create(user=self.user)
|
||||
self.config.accessible_views.add(self.sign_view)
|
||||
UserThematics.objects.create(
|
||||
user_config=self.config,
|
||||
thematic=self.sign_thematic,
|
||||
can_view_assets=True,
|
||||
can_edit_assets=True,
|
||||
)
|
||||
|
||||
# Organization
|
||||
self.org = Organization.objects.create(
|
||||
code="GAN",
|
||||
name_fr="Ganshoren",
|
||||
name_nl="Ganshoren",
|
||||
organization_type="municipality",
|
||||
)
|
||||
|
||||
# Category
|
||||
self.category = AssetCategory.objects.create(
|
||||
code="sign_cat_test",
|
||||
name_fr="Catégorie Signalisation Test",
|
||||
)
|
||||
|
||||
# Municipality
|
||||
self.muni = Municipality.objects.create(
|
||||
fid="1083",
|
||||
name_fr="Ganshoren",
|
||||
name_nl="Ganshoren",
|
||||
)
|
||||
|
||||
# AdrStreet (the actual address street in common)
|
||||
poly = Polygon(((145000, 172000), (145000, 172500), (145500, 172500), (145500, 172000), (145000, 172000)))
|
||||
self.adr_street = AdrStreet.objects.create(
|
||||
fid="ADR-GAN-001",
|
||||
name_fr="Avenue Charles-Quint",
|
||||
name_nl="Keizer Karellaan",
|
||||
municipality=self.muni,
|
||||
geom=MultiPolygon(poly, srid=3812),
|
||||
centroid=Point(145250, 172250, srid=3812),
|
||||
)
|
||||
|
||||
# SignStreet (the road network geometry layer)
|
||||
self.sign_street = SignStreet.objects.create(
|
||||
code="GAN_R20_001",
|
||||
name_fr="Avenue Charles-Quint",
|
||||
name_nl="Keizer Karellaan",
|
||||
municipality=self.muni,
|
||||
geom=MultiPolygon(poly, srid=3812),
|
||||
)
|
||||
|
||||
# Pole and panel models
|
||||
self.pole_model = SignPoleModel.objects.create(
|
||||
code="PM-TEST",
|
||||
name_fr="Poteau Standard Test",
|
||||
category=self.category,
|
||||
height=3.0,
|
||||
)
|
||||
|
||||
self.panel_model = SignPanelModel.objects.create(
|
||||
code="PANM-TEST",
|
||||
name_fr="Modèle C1 Test",
|
||||
category=self.category,
|
||||
shape="rond",
|
||||
)
|
||||
|
||||
self.panel_type = SignPanelType.objects.create(
|
||||
code="C1",
|
||||
name_fr="Sens interdit",
|
||||
name_nl="Verboden richting",
|
||||
default_width=60.0,
|
||||
default_height=60.0,
|
||||
)
|
||||
|
||||
def test_get_closest_adr_street(self):
|
||||
from sign.views import get_closest_adr_street
|
||||
from django.contrib.gis.geos import Point
|
||||
|
||||
# Test resolving using SignStreet directly
|
||||
adr = get_closest_adr_street(sign_street=self.sign_street)
|
||||
self.assertEqual(adr, self.adr_street)
|
||||
|
||||
# Test resolving using spatial coords in Lambert 2008 (3812)
|
||||
pt_3812 = Point(145250, 172250, srid=3812)
|
||||
adr_pt = get_closest_adr_street(point=pt_3812)
|
||||
self.assertEqual(adr_pt, self.adr_street)
|
||||
|
||||
def test_sign_pole_create_assigns_adr_street(self):
|
||||
"""Test adding a pole at Charles-Quint does not crash with SignStreet assignment error."""
|
||||
from django.contrib.gis.geos import Point
|
||||
self.client.force_login(self.user)
|
||||
pt = Point(145250, 172250, srid=3812)
|
||||
pt.transform(4326)
|
||||
|
||||
response = self.client.post(reverse('sign:sign_pole_create'), {
|
||||
'model': self.pole_model.id,
|
||||
'organization': self.org.id,
|
||||
'orientation': 0,
|
||||
'lat': pt.y,
|
||||
'lon': pt.x,
|
||||
})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
data = response.json()
|
||||
self.assertTrue(data.get('success'))
|
||||
|
||||
from sign.models import SignPole
|
||||
pole = SignPole.objects.get(id=data['id'])
|
||||
# Must be an instance of AdrStreet, not SignStreet
|
||||
from common.models import AdrStreet
|
||||
self.assertIsInstance(pole.street, AdrStreet)
|
||||
self.assertEqual(pole.street, self.adr_street)
|
||||
|
||||
def test_sign_panel_create_assigns_adr_street(self):
|
||||
"""Test adding a panel at Charles-Quint assigns AdrStreet correctly."""
|
||||
from django.contrib.gis.geos import Point
|
||||
self.client.force_login(self.user)
|
||||
pt = Point(145250, 172250, srid=3812)
|
||||
pt.transform(4326)
|
||||
|
||||
response = self.client.post(reverse('sign:sign_panel_create'), {
|
||||
'signpanel_type': self.panel_type.id,
|
||||
'model': self.panel_model.id,
|
||||
'organization': self.org.id,
|
||||
'orientation': 0,
|
||||
'support_type': 'pole',
|
||||
'lat': pt.y,
|
||||
'lon': pt.x,
|
||||
})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
data = response.json()
|
||||
self.assertTrue(data.get('success'))
|
||||
|
||||
from sign.models import SignPanel
|
||||
panel = SignPanel.objects.get(id=data['id'])
|
||||
from common.models import AdrStreet
|
||||
self.assertIsInstance(panel.street, AdrStreet)
|
||||
self.assertEqual(panel.street, self.adr_street)
|
||||
|
||||
def test_sign_panel_create_attached_to_pole(self):
|
||||
"""Test adding a panel attached to an existing pole inherits pole's street and sets vertical order."""
|
||||
from django.contrib.gis.geos import Point
|
||||
from sign.models import SignPole, SignPanel
|
||||
from common.models import AdrStreet
|
||||
|
||||
pt = Point(145250, 172250, srid=3812)
|
||||
pole = SignPole.objects.create(
|
||||
street=self.adr_street,
|
||||
model=self.pole_model,
|
||||
organization=self.org,
|
||||
geom=pt,
|
||||
lon=4.31,
|
||||
lat=50.86,
|
||||
)
|
||||
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.post(reverse('sign:sign_panel_create'), {
|
||||
'pole_id': pole.id,
|
||||
'signpanel_type': self.panel_type.id,
|
||||
'model': self.panel_model.id,
|
||||
'organization': self.org.id,
|
||||
'orientation': 0,
|
||||
'support_type': 'pole',
|
||||
})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
data = response.json()
|
||||
self.assertTrue(data.get('success'))
|
||||
|
||||
panel = SignPanel.objects.get(id=data['id'])
|
||||
self.assertEqual(panel.pole, pole)
|
||||
self.assertEqual(panel.street, self.adr_street)
|
||||
self.assertIsInstance(panel.street, AdrStreet)
|
||||
self.assertEqual(panel.vertical_order, 1)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ try:
|
|||
except ImportError:
|
||||
Image = None
|
||||
|
||||
from common.models import UserConfig, UserThematics, Municipality
|
||||
from common.models import UserConfig, UserThematics, Municipality, AdrStreet
|
||||
from .models import (
|
||||
SignPanelType,
|
||||
SignStreet,
|
||||
|
|
@ -36,6 +36,103 @@ from .permissions import user_can_manage_regulations, user_can_edit_regulation,
|
|||
from .forms import SignPoleForm, SignPanelForm
|
||||
|
||||
|
||||
def get_closest_adr_street(lat=None, lon=None, point=None, sign_street=None):
|
||||
"""
|
||||
Resolve corresponding common.AdrStreet from coordinates, Point, or SignStreet.
|
||||
1. If sign_street is provided or can be found via spatial proximity, match by name or geom.
|
||||
2. Fallback to point geometry directly on AdrStreet.
|
||||
"""
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
|
||||
# 1. If sign_street is not provided, try to find the closest SignStreet spatially
|
||||
if sign_street is None and (point is not None or (lat is not None and lon is not None)):
|
||||
if point is None:
|
||||
point = Point(float(lon), float(lat), srid=4326)
|
||||
point_3812 = point.clone()
|
||||
if point_3812.srid != 3812:
|
||||
point_3812.transform(3812)
|
||||
try:
|
||||
sign_street = (
|
||||
SignStreet.objects.filter(geom__isnull=False)
|
||||
.annotate(distance=Distance('geom', point_3812))
|
||||
.order_by('distance')
|
||||
.first()
|
||||
)
|
||||
except Exception:
|
||||
sign_street = None
|
||||
|
||||
if sign_street:
|
||||
# Match by French name
|
||||
if sign_street.name_fr:
|
||||
qs = AdrStreet.objects.filter(name_fr__iexact=sign_street.name_fr.strip())
|
||||
if sign_street.municipality_id:
|
||||
adr = qs.filter(municipality_id=sign_street.municipality_id).first()
|
||||
if adr:
|
||||
return adr
|
||||
adr = qs.first()
|
||||
if adr:
|
||||
return adr
|
||||
|
||||
# Match by Dutch name
|
||||
if sign_street.name_nl:
|
||||
qs = AdrStreet.objects.filter(name_nl__iexact=sign_street.name_nl.strip())
|
||||
if sign_street.municipality_id:
|
||||
adr = qs.filter(municipality_id=sign_street.municipality_id).first()
|
||||
if adr:
|
||||
return adr
|
||||
adr = qs.first()
|
||||
if adr:
|
||||
return adr
|
||||
|
||||
# Match by proximity between SignStreet geom and AdrStreet geom
|
||||
if sign_street.geom:
|
||||
try:
|
||||
adr = (
|
||||
AdrStreet.objects.filter(geom__isnull=False)
|
||||
.annotate(distance=Distance('geom', sign_street.geom))
|
||||
.order_by('distance')
|
||||
.first()
|
||||
)
|
||||
if adr:
|
||||
return adr
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Fallback to point geometry directly on AdrStreet
|
||||
if point is not None or (lat is not None and lon is not None):
|
||||
if point is None:
|
||||
point = Point(float(lon), float(lat), srid=4326)
|
||||
point_3812 = point.clone()
|
||||
if point_3812.srid != 3812:
|
||||
point_3812.transform(3812)
|
||||
try:
|
||||
adr = (
|
||||
AdrStreet.objects.filter(geom__isnull=False)
|
||||
.annotate(distance=Distance('geom', point_3812))
|
||||
.order_by('distance')
|
||||
.first()
|
||||
)
|
||||
if adr:
|
||||
return adr
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
adr = (
|
||||
AdrStreet.objects.filter(centroid__isnull=False)
|
||||
.annotate(distance=Distance('centroid', point_3812))
|
||||
.order_by('distance')
|
||||
.first()
|
||||
)
|
||||
if adr:
|
||||
return adr
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_grouped_panel_types():
|
||||
panel_types = list(SignPanelType.objects.all().order_by('code'))
|
||||
|
||||
|
|
@ -2096,7 +2193,6 @@ def regulation_abrogation_article_voiries_suggest(request, regulation_id: int, a
|
|||
@login_required
|
||||
def sign_pole_create(request):
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
|
||||
user_config = get_object_or_404(UserConfig, user=request.user)
|
||||
if not user_config.can_access_view('sign') or not user_can_edit_esign_assets(request.user):
|
||||
|
|
@ -2111,29 +2207,20 @@ def sign_pole_create(request):
|
|||
lat = form.cleaned_data.get('lat')
|
||||
lon = form.cleaned_data.get('lon')
|
||||
|
||||
# But let's ensure it's correct with explicit float conversion
|
||||
# Ensure correct coordinates and projection (Lambert 2008: SRID 3812)
|
||||
if lat and lon:
|
||||
pole.lon = float(lon)
|
||||
pole.lat = float(lat)
|
||||
pole.geom = Point(float(lon), float(lat), srid=4326)
|
||||
pole.geojson = pole.geom.geojson
|
||||
pole.geom.transform(3852)
|
||||
pole.geom.transform(3812)
|
||||
|
||||
# If street is not provided, find the closest street
|
||||
if not pole.street_id:
|
||||
if lat and lon:
|
||||
pole_point = Point(float(lon), float(lat), srid=4326)
|
||||
closest_street = SignStreet.objects.annotate(
|
||||
distance=Distance('geom', pole_point)
|
||||
).order_by('distance').first()
|
||||
if closest_street:
|
||||
pole.street = closest_street
|
||||
else:
|
||||
return JsonResponse({'success': False, 'errors': {'street': [_('Aucune rue trouvée à proximité.')]}}, status=400)
|
||||
else:
|
||||
return JsonResponse({'success': False, 'errors': {'geom': [_('Les coordonnées sont requises.')]}}, status=400)
|
||||
# If street is not provided, find the closest AdrStreet
|
||||
if not pole.street_id and lat and lon:
|
||||
adr_street = get_closest_adr_street(lat=lat, lon=lon)
|
||||
if adr_street:
|
||||
pole.street = adr_street
|
||||
|
||||
# Now save for the FIRST time (commit=True)
|
||||
pole.save()
|
||||
return JsonResponse({'success': True, 'id': pole.id, 'message': _('Poteau créé avec succès.')})
|
||||
except Exception as e:
|
||||
|
|
@ -2174,7 +2261,11 @@ def sign_pole_edit(request, pole_id):
|
|||
pole.lat = float(lat)
|
||||
pole.geom = Point(float(lon), float(lat), srid=4326)
|
||||
pole.geojson = pole.geom.geojson
|
||||
pole.geom.transform(3852)
|
||||
pole.geom.transform(3812)
|
||||
if not pole.street_id:
|
||||
adr_street = get_closest_adr_street(lat=lat, lon=lon)
|
||||
if adr_street:
|
||||
pole.street = adr_street
|
||||
|
||||
pole.save()
|
||||
return JsonResponse({'success': True, 'id': pole.id, 'message': _('Poteau modifié avec succès.')})
|
||||
|
|
@ -2205,7 +2296,6 @@ def sign_pole_edit(request, pole_id):
|
|||
@login_required
|
||||
def sign_panel_create(request):
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
|
||||
user_config = get_object_or_404(UserConfig, user=request.user)
|
||||
if not user_config.can_access_view('sign') or not user_can_edit_esign_assets(request.user):
|
||||
|
|
@ -2214,31 +2304,51 @@ def sign_panel_create(request):
|
|||
if request.method == 'POST':
|
||||
form = SignPanelForm(request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
panel = form.save(commit=False)
|
||||
lat = form.cleaned_data.get('lat')
|
||||
lon = form.cleaned_data.get('lon')
|
||||
|
||||
# If pole_id is provided, link it and inherit its street
|
||||
# If pole_id is provided, link it and inherit its street and geom
|
||||
pole_id = request.POST.get('pole_id')
|
||||
if pole_id:
|
||||
try:
|
||||
pole = SignPole.objects.get(id=pole_id)
|
||||
panel.pole = pole
|
||||
panel.street = pole.street
|
||||
if pole.geom:
|
||||
panel.geom = pole.geom
|
||||
panel.lon = pole.lon
|
||||
panel.lat = pole.lat
|
||||
panel.geojson = pole.geojson
|
||||
except SignPole.DoesNotExist:
|
||||
pass
|
||||
|
||||
# If street is still not set and we have coordinates, find the closest street
|
||||
if lat and lon and not panel.street_id:
|
||||
panel_point = Point(lon, lat, srid=4326)
|
||||
closest_street = SignStreet.objects.annotate(
|
||||
distance=Distance('geom', panel_point)
|
||||
).order_by('distance').first()
|
||||
if closest_street:
|
||||
panel.street = closest_street
|
||||
# If coordinates are provided and geom not yet set
|
||||
if lat and lon and not panel.geom:
|
||||
panel.lon = float(lon)
|
||||
panel.lat = float(lat)
|
||||
panel.geom = Point(float(lon), float(lat), srid=4326)
|
||||
panel.geojson = panel.geom.geojson
|
||||
panel.geom.transform(3812)
|
||||
|
||||
# If street is still not set and we have coordinates, find the closest AdrStreet
|
||||
if not panel.street_id and lat and lon:
|
||||
adr_street = get_closest_adr_street(lat=lat, lon=lon)
|
||||
if adr_street:
|
||||
panel.street = adr_street
|
||||
|
||||
# Set vertical order if attached to a pole
|
||||
if panel.pole and panel.vertical_order is None:
|
||||
max_order = panel.pole.sign_app_panels.aggregate(m=Max('vertical_order'))['m'] or 0
|
||||
panel.vertical_order = max_order + 1
|
||||
|
||||
panel.save()
|
||||
return JsonResponse({'success': True, 'id': panel.id, 'message': _('Panneau créé avec succès.')})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return JsonResponse({'success': False, 'errors': {'__all__': [str(e)]}}, status=500)
|
||||
else:
|
||||
return JsonResponse({'success': False, 'errors': form.errors}, status=400)
|
||||
else:
|
||||
|
|
|
|||
Loading…
Reference in a new issue