Skip to content

Commit

Permalink
[Dépôt de besoin] Conservation des données des dépôts abandonnés (#938)
Browse files Browse the repository at this point in the history
* keep steps data

* add admin for tender steps data

* add tests to check steps data

* hide personal data

* set right permissions

* clean step datas after publication

* also remove steps data in csrf way

* add FIELDS_TO_REDACT list to model
  • Loading branch information
SebastienReuiller authored Oct 10, 2023
1 parent b6b8223 commit 1a1df29
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 4 deletions.
31 changes: 30 additions & 1 deletion lemarche/tenders/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from lemarche.perimeters.admin import PerimeterRegionFilter
from lemarche.tenders import constants
from lemarche.tenders.forms import TenderAdminForm
from lemarche.tenders.models import PartnerShareTender, Tender, TenderQuestion
from lemarche.tenders.models import PartnerShareTender, Tender, TenderQuestion, TenderStepsData
from lemarche.utils.admin.admin_site import admin_site
from lemarche.utils.apis import api_hubspot
from lemarche.utils.fields import ChoiceArrayField, pretty_print_readonly_jsonfield
Expand Down Expand Up @@ -591,3 +591,32 @@ def logs_display(self, partnersharetender=None):
return "-"

logs_display.short_description = PartnerShareTender._meta.get_field("logs").verbose_name


@admin.register(TenderStepsData, site=admin_site)
class TenderStepsDataAdmin(admin.ModelAdmin):
list_display = ["created_at", "updated_at", "uuid"]

readonly_fields = [
"id",
"created_at",
"updated_at",
"uuid",
"steps_data_display",
]

def steps_data_display(self, tender_steps_data: TenderStepsData = None):
if tender_steps_data:
return pretty_print_readonly_jsonfield(tender_steps_data.steps_data)
return "-"

steps_data_display.short_description = "Données saisies dans les étapes"

def has_add_permission(self, request):
return False

def has_change_permission(self, request, obj=None):
return False

def has_delete_permission(self, request, obj=None):
return False
43 changes: 43 additions & 0 deletions lemarche/tenders/migrations/0058_tenderstepsdata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 4.2.2 on 2023-10-09 13:35

import django.utils.timezone
from django.db import migrations, models
from django_extensions.db.fields import ShortUUIDField
from shortuuid import uuid


class Migration(migrations.Migration):
dependencies = [
("tenders", "0057_alter_tender_siae_transactioned"),
]

operations = [
migrations.CreateModel(
name="TenderStepsData",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"uuid",
ShortUUIDField(
auto_created=True,
blank=True,
db_index=True,
default=uuid,
editable=False,
unique=True,
verbose_name="Identifiant UUID",
),
),
(
"created_at",
models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date de création"),
),
("updated_at", models.DateTimeField(auto_now=True, verbose_name="Date de modification")),
("steps_data", models.JSONField(default=list, editable=False, verbose_name="Données des étapes")),
],
options={
"verbose_name": "Besoin d'achat - Données des étapes",
"verbose_name_plural": "Besoins d'achat - Données des étapes",
},
),
]
30 changes: 30 additions & 0 deletions lemarche/tenders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from django.utils.functional import cached_property
from django.utils.text import slugify
from django_better_admin_arrayfield.models.fields import ArrayField
from django_extensions.db.fields import ShortUUIDField
from shortuuid import uuid

from lemarche.perimeters.models import Perimeter
from lemarche.siaes import constants as siae_constants
Expand Down Expand Up @@ -761,3 +763,31 @@ class Meta:
@cached_property
def perimeters_list_string(self) -> str:
return ", ".join(self.perimeters.values_list("name", flat=True))


class TenderStepsData(models.Model):
FIELDS_TO_REDACT = [
"contact-contact_email",
"contact-contact_phone",
"contact-contact_last_name",
"contact-contact_first_name",
]

created_at = models.DateTimeField(verbose_name="Date de création", default=timezone.now)
updated_at = models.DateTimeField(verbose_name="Date de modification", auto_now=True)
uuid = ShortUUIDField(
verbose_name="Identifiant UUID",
default=uuid,
editable=False,
unique=True,
db_index=True,
auto_created=True,
)
steps_data = models.JSONField(verbose_name="Données des étapes", editable=False, default=list)

class Meta:
verbose_name = "Besoin d'achat - Données des étapes"
verbose_name_plural = "Besoins d'achat - Données des étapes"

def __str__(self):
return f"{self.uuid} - {self.created_at}"
7 changes: 6 additions & 1 deletion lemarche/www/pages/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from lemarche.sectors.models import Sector
from lemarche.siaes.models import Siae, SiaeGroup
from lemarche.tenders import constants as tender_constants
from lemarche.tenders.models import Tender
from lemarche.tenders.models import Tender, TenderStepsData
from lemarche.users.models import User
from lemarche.utils.tracker import track
from lemarche.www.pages.forms import (
Expand Down Expand Up @@ -357,6 +357,11 @@ def csrf_failure(request, reason=""): # noqa C901
tender.save()
tender.set_siae_found_list()

# remove steps data
uuid = request.session.get("tender_steps_data_uuid", None)
if uuid:
TenderStepsData.objects.filter(uuid=uuid).delete()

if settings.BITOUBI_ENV == "prod":
notify_admin_tender_created(tender)

Expand Down
11 changes: 10 additions & 1 deletion lemarche/www/tenders/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from lemarche.siaes.models import Siae
from lemarche.tenders import constants as tender_constants
from lemarche.tenders.factories import TenderFactory, TenderQuestionFactory
from lemarche.tenders.models import Tender, TenderSiae
from lemarche.tenders.models import Tender, TenderSiae, TenderStepsData
from lemarche.users.factories import UserFactory
from lemarche.users.models import User
from lemarche.www.tenders.views import TenderCreateMultiStepView
Expand Down Expand Up @@ -83,12 +83,21 @@ def _check_every_step(self, tenders_step_data, final_redirect_page: str = revers
# make sure that after the create tender we are redirected to ??
self.assertEqual(response.status_code, 200)
self.assertRedirects(response, final_redirect_page)
# has the step datas been cleaned ?
self.assertEqual(TenderStepsData.objects.count(), 0)
return response
else:
self.assertEqual(response.status_code, 200)
current_errors = response.context_data["form"].errors
self.assertEquals(current_errors, {})

# Is the step data stored correctly ?
tender_step_data = TenderStepsData.objects.first()
self.assertEqual(
data_step["tender_create_multi_step_view-current_step"],
tender_step_data.steps_data[-1]["tender_create_multi_step_view-current_step"],
)

def test_anyone_can_access_create_tender(self):
# anonymous
url = reverse("tenders:create")
Expand Down
36 changes: 35 additions & 1 deletion lemarche/www/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from lemarche.siaes.models import Siae
from lemarche.tenders import constants as tender_constants
from lemarche.tenders.models import Tender, TenderSiae
from lemarche.tenders.models import Tender, TenderSiae, TenderStepsData
from lemarche.users.models import User
from lemarche.utils.data import get_choice
from lemarche.utils.mixins import (
Expand Down Expand Up @@ -148,6 +148,35 @@ def get_context_data(self, form, **kwargs):
context.update({"tender": tender_dict})
return context

def process_step(self, form):
"""
Save step data
"""
data = form.data.copy()
if "csrfmiddlewaretoken" in data:
del data["csrfmiddlewaretoken"]

# Hide personal data
for field_to_redacted in TenderStepsData.FIELDS_TO_REDACT:
if field_to_redacted in data:
data[field_to_redacted] = "[REDACTED]"

data["timestamp"] = timezone.now().isoformat()

uuid = self.request.session.get("tender_steps_data_uuid", None)
if uuid:
try:
tender_steps_data = TenderStepsData.objects.get(uuid=uuid)
tender_steps_data.steps_data.append(data)
tender_steps_data.save()
except TenderStepsData.DoesNotExist:
tender_steps_data = TenderStepsData.objects.create(uuid=uuid, steps_data=[data])
else:
tender_steps_data = TenderStepsData.objects.create(steps_data=[data])
self.request.session["tender_steps_data_uuid"] = tender_steps_data.uuid

return form.data

def save_instance_tender(self, tender_dict: dict, form_dict: dict, is_draft: bool):
tender_status = tender_constants.STATUS_DRAFT if is_draft else tender_constants.STATUS_PUBLISHED
tender_published_at = None if is_draft else timezone.now()
Expand Down Expand Up @@ -201,6 +230,11 @@ def done(self, _, form_dict, **kwargs):
self.save_instance_tender(tender_dict=tender_dict, form_dict=form_dict, is_draft=is_draft)
self.instance.set_siae_found_list()

# remove steps data
uuid = self.request.session.get("tender_steps_data_uuid", None)
if uuid:
TenderStepsData.objects.filter(uuid=uuid).delete()

# we notify the admin team
if settings.BITOUBI_ENV == "prod":
notify_admin_tender_created(self.instance)
Expand Down

0 comments on commit 1a1df29

Please sign in to comment.