irie 0.0.0__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 irie might be problematic. Click here for more details.

Files changed (145) hide show
  1. irie/__main__.py +24 -0
  2. irie/apps/__init__.py +5 -0
  3. irie/apps/authentication/__init__.py +1 -0
  4. irie/apps/authentication/admin.py +1 -0
  5. irie/apps/authentication/config.py +6 -0
  6. irie/apps/authentication/forms.py +41 -0
  7. irie/apps/authentication/migrations/__init__.py +1 -0
  8. irie/apps/authentication/models.py +1 -0
  9. irie/apps/authentication/tests.py +1 -0
  10. irie/apps/authentication/urls.py +9 -0
  11. irie/apps/authentication/views.py +53 -0
  12. irie/apps/config.py +8 -0
  13. irie/apps/context_processors.py +5 -0
  14. irie/apps/documents/__init__.py +0 -0
  15. irie/apps/documents/apps.py +7 -0
  16. irie/apps/documents/documents.py +61 -0
  17. irie/apps/documents/migrations/__init__.py +0 -0
  18. irie/apps/documents/tests.py +3 -0
  19. irie/apps/documents/urls.py +12 -0
  20. irie/apps/documents/views.py +27 -0
  21. irie/apps/evaluation/__init__.py +0 -0
  22. irie/apps/evaluation/admin.py +43 -0
  23. irie/apps/evaluation/apps.py +18 -0
  24. irie/apps/evaluation/daemon.py +107 -0
  25. irie/apps/evaluation/identification.py +196 -0
  26. irie/apps/evaluation/migrations/0001_initial.py +25 -0
  27. irie/apps/evaluation/migrations/0002_remove_evaluation_cesmd.py +17 -0
  28. irie/apps/evaluation/migrations/0003_evaluation_asset.py +20 -0
  29. irie/apps/evaluation/migrations/__init__.py +0 -0
  30. irie/apps/evaluation/models.py +72 -0
  31. irie/apps/evaluation/urls.py +16 -0
  32. irie/apps/evaluation/views.py +68 -0
  33. irie/apps/events/__init__.py +0 -0
  34. irie/apps/events/admin.py +9 -0
  35. irie/apps/events/apps.py +12 -0
  36. irie/apps/events/migrations/0001_initial.py +27 -0
  37. irie/apps/events/migrations/0002_alter_event_id.py +18 -0
  38. irie/apps/events/migrations/0003_event_cesmd.py +19 -0
  39. irie/apps/events/migrations/0004_event_record_identifier.py +19 -0
  40. irie/apps/events/migrations/0005_event_asset.py +21 -0
  41. irie/apps/events/migrations/0006_alter_event_event_file.py +18 -0
  42. irie/apps/events/migrations/__init__.py +0 -0
  43. irie/apps/events/models.py +70 -0
  44. irie/apps/events/tests.py +1 -0
  45. irie/apps/events/tests_events.py +240 -0
  46. irie/apps/events/urls.py +29 -0
  47. irie/apps/events/views.py +55 -0
  48. irie/apps/events/views_events.py +215 -0
  49. irie/apps/inventory/CESMD.py +81 -0
  50. irie/apps/inventory/__init__.py +5 -0
  51. irie/apps/inventory/admin.py +10 -0
  52. irie/apps/inventory/apps.py +12 -0
  53. irie/apps/inventory/archive/arcGIS.py +1175 -0
  54. irie/apps/inventory/calid.py +65 -0
  55. irie/apps/inventory/fields.py +5 -0
  56. irie/apps/inventory/forms.py +12 -0
  57. irie/apps/inventory/migrations/0001_initial.py +31 -0
  58. irie/apps/inventory/migrations/0002_assetevaluationmodel_cesmd.py +19 -0
  59. irie/apps/inventory/migrations/0003_auto_20230520_2030.py +23 -0
  60. irie/apps/inventory/migrations/0004_asset.py +27 -0
  61. irie/apps/inventory/migrations/0005_auto_20230731_1802.py +23 -0
  62. irie/apps/inventory/migrations/0006_auto_20230731_1816.py +28 -0
  63. irie/apps/inventory/migrations/0007_auto_20230731_1827.py +24 -0
  64. irie/apps/inventory/migrations/0008_asset_is_complete.py +19 -0
  65. irie/apps/inventory/migrations/0009_auto_20230731_1842.py +29 -0
  66. irie/apps/inventory/migrations/0010_auto_20230801_0025.py +23 -0
  67. irie/apps/inventory/migrations/0011_alter_asset_cgs_data.py +18 -0
  68. irie/apps/inventory/migrations/0012_corridor.py +22 -0
  69. irie/apps/inventory/migrations/0013_alter_asset_cesmd.py +18 -0
  70. irie/apps/inventory/migrations/0014_alter_asset_cesmd.py +18 -0
  71. irie/apps/inventory/migrations/__init__.py +0 -0
  72. irie/apps/inventory/models.py +70 -0
  73. irie/apps/inventory/tables.py +584 -0
  74. irie/apps/inventory/traffic.py +175052 -0
  75. irie/apps/inventory/urls.py +25 -0
  76. irie/apps/inventory/views.py +515 -0
  77. irie/apps/management/__init__.py +0 -0
  78. irie/apps/management/commands/__init__.py +0 -0
  79. irie/apps/networks/__init__.py +0 -0
  80. irie/apps/networks/apps.py +5 -0
  81. irie/apps/networks/forms.py +64 -0
  82. irie/apps/networks/migrations/0001_initial.py +26 -0
  83. irie/apps/networks/migrations/__init__.py +0 -0
  84. irie/apps/networks/models.py +14 -0
  85. irie/apps/networks/networks.py +782 -0
  86. irie/apps/networks/tests.py +1 -0
  87. irie/apps/networks/urls.py +18 -0
  88. irie/apps/networks/views.py +89 -0
  89. irie/apps/prediction/__init__.py +0 -0
  90. irie/apps/prediction/admin.py +9 -0
  91. irie/apps/prediction/apps.py +12 -0
  92. irie/apps/prediction/forms.py +20 -0
  93. irie/apps/prediction/metrics.py +61 -0
  94. irie/apps/prediction/migrations/0001_initial.py +32 -0
  95. irie/apps/prediction/migrations/0002_auto_20230731_1801.py +27 -0
  96. irie/apps/prediction/migrations/0003_rename_assetevaluationmodel_evaluation.py +18 -0
  97. irie/apps/prediction/migrations/0004_delete_evaluation.py +16 -0
  98. irie/apps/prediction/migrations/0005_predictormodel_protocol.py +18 -0
  99. irie/apps/prediction/migrations/0006_alter_predictormodel_protocol.py +18 -0
  100. irie/apps/prediction/migrations/0007_predictormodel_active.py +19 -0
  101. irie/apps/prediction/migrations/0008_predictormodel_description.py +18 -0
  102. irie/apps/prediction/migrations/0009_predictormodel_entry_point.py +19 -0
  103. irie/apps/prediction/migrations/0010_alter_predictormodel_entry_point.py +18 -0
  104. irie/apps/prediction/migrations/0011_remove_predictormodel_entry_point.py +17 -0
  105. irie/apps/prediction/migrations/0012_predictormodel_entry_point.py +18 -0
  106. irie/apps/prediction/migrations/0013_predictormodel_metrics.py +18 -0
  107. irie/apps/prediction/migrations/0014_auto_20240930_0004.py +28 -0
  108. irie/apps/prediction/migrations/0015_alter_predictormodel_render_file.py +18 -0
  109. irie/apps/prediction/migrations/__init__.py +0 -0
  110. irie/apps/prediction/models.py +37 -0
  111. irie/apps/prediction/predictor.py +286 -0
  112. irie/apps/prediction/runners/__init__.py +450 -0
  113. irie/apps/prediction/runners/metrics.py +168 -0
  114. irie/apps/prediction/runners/opensees/__init__.py +0 -0
  115. irie/apps/prediction/runners/opensees/schemas/__init__.py +39 -0
  116. irie/apps/prediction/runners/utilities.py +277 -0
  117. irie/apps/prediction/runners/xmlutils.py +232 -0
  118. irie/apps/prediction/runners/zipped.py +27 -0
  119. irie/apps/prediction/templatetags/__init__.py +0 -0
  120. irie/apps/prediction/templatetags/predictor.py +20 -0
  121. irie/apps/prediction/urls.py +19 -0
  122. irie/apps/prediction/views.py +184 -0
  123. irie/apps/prediction/views_api.py +216 -0
  124. irie/apps/site/__init__.py +0 -0
  125. irie/apps/site/admin.py +1 -0
  126. irie/apps/site/config.py +6 -0
  127. irie/apps/site/migrations/__init__.py +1 -0
  128. irie/apps/site/models.py +2 -0
  129. irie/apps/site/templatetags/__init__.py +0 -0
  130. irie/apps/site/templatetags/indexing.py +7 -0
  131. irie/apps/site/tests.py +1 -0
  132. irie/apps/site/urls.py +8 -0
  133. irie/apps/site/view_sdof.py +40 -0
  134. irie/apps/site/view_utils.py +13 -0
  135. irie/apps/site/views.py +88 -0
  136. irie/core/__init__.py +5 -0
  137. irie/core/asgi.py +12 -0
  138. irie/core/settings.py +223 -0
  139. irie/core/urls.py +39 -0
  140. irie/core/wsgi.py +12 -0
  141. irie-0.0.0.dist-info/METADATA +48 -0
  142. irie-0.0.0.dist-info/RECORD +145 -0
  143. irie-0.0.0.dist-info/WHEEL +5 -0
  144. irie-0.0.0.dist-info/entry_points.txt +2 -0
  145. irie-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,25 @@
1
+ # Generated by Django 3.2.13 on 2023-08-07 19:20
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ('apps_events', '0005_event_asset'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name='Evaluation',
18
+ fields=[
19
+ ('id', models.BigAutoField(primary_key=True, serialize=False)),
20
+ ('cesmd', models.CharField(max_length=7)),
21
+ ('evaluation_data', models.JSONField(default=dict)),
22
+ ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='apps_events.event')),
23
+ ],
24
+ ),
25
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by Django 3.2.13 on 2023-08-07 19:21
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('apps_evaluation', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='evaluation',
15
+ name='cesmd',
16
+ ),
17
+ ]
@@ -0,0 +1,20 @@
1
+ # Generated by Django 3.2.13 on 2024-01-06 04:32
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('apps_inventory', '0011_alter_asset_cgs_data'),
11
+ ('apps_evaluation', '0002_remove_evaluation_cesmd'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='evaluation',
17
+ name='asset',
18
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='apps_inventory.asset'),
19
+ ),
20
+ ]
File without changes
@@ -0,0 +1,72 @@
1
+ #===----------------------------------------------------------------------===#
2
+ #
3
+ # STAIRLab -- STructural Artificial Intelligence Laboratory
4
+ #
5
+ #===----------------------------------------------------------------------===#
6
+ #
7
+ # Summer 2023, BRACE2 Team
8
+ # Berkeley, CA
9
+ #
10
+ #----------------------------------------------------------------------------#
11
+ import sys
12
+ import logging
13
+
14
+ from threading import Thread
15
+ from django.db import models
16
+
17
+ from apps.events.models import Event
18
+ from apps.inventory.models import Asset
19
+ from .daemon import LiveEvaluation
20
+
21
+ class Evaluation(models.Model):
22
+ id = models.BigAutoField(primary_key=True)
23
+ event = models.ForeignKey(Event, on_delete=models.CASCADE)
24
+ asset = models.ForeignKey(Asset, on_delete=models.CASCADE, null=True)
25
+ evaluation_data = models.JSONField(default=dict)
26
+
27
+ @classmethod
28
+ def create(cls, event: Event, asset: Asset, data: dict = None):
29
+ evaluation = cls()
30
+ evaluation.event = event
31
+ evaluation.asset = asset
32
+ evaluation.save()
33
+
34
+ if data is not None:
35
+ evaluation.evaluation_data = data["evaluation_data"]
36
+ evaluation.save()
37
+ else:
38
+ Thread(target=evaluate, args=(event, evaluation)).start()
39
+
40
+ return evaluation
41
+
42
+
43
+ def evaluate(event, evaluation)->"Evaluation":
44
+
45
+ daemon = LiveEvaluation(event, event.asset.predictors, evaluation)
46
+
47
+ # assignPredictorMetrics?
48
+ predictions = daemon.assignMetricPredictors(event)
49
+
50
+ #with multiprocessing.dummy.Pool(3) as pool:
51
+ # for predictor_name, run_id in pool.imap_unordered(
52
+ # self.runPredictor,
53
+ # predictions.items()
54
+ # ):
55
+ if True:
56
+ for predictor_name, run_id in map(
57
+ daemon.runPredictor,
58
+ predictions.items()
59
+ ):
60
+
61
+ print(f">>> {predictor_name} run complete.", file=sys.stderr)
62
+ for mname in predictions[predictor_name][1]:
63
+ print(f">>> Retrieving {predictor_name}/{mname}.", file=sys.stderr)
64
+ data = daemon.predictors[predictor_name].getMetricData(run_id, mname)
65
+ daemon.setMetricData(mname, predictor_name, data)
66
+
67
+ print(f">>> Completed {predictor_name}.", file=sys.stderr)
68
+
69
+
70
+ daemon.save()
71
+ return evaluation
72
+
@@ -0,0 +1,16 @@
1
+ #===----------------------------------------------------------------------===#
2
+ #
3
+ # STAIRLab -- STructural Artificial Intelligence Laboratory
4
+ #
5
+ #===----------------------------------------------------------------------===#
6
+ #
7
+ # Summer 2023, BRACE2 Team
8
+ # Berkeley, CA
9
+ #
10
+ #----------------------------------------------------------------------------#
11
+ from django.urls import path
12
+ from .views import get_evaluations
13
+
14
+ urlpatterns = [
15
+ path("api/evals/", get_evaluations),
16
+ ]
@@ -0,0 +1,68 @@
1
+ #===----------------------------------------------------------------------===#
2
+ #
3
+ # STAIRLab -- STructural Artificial Intelligence Laboratory
4
+ #
5
+ #===----------------------------------------------------------------------===#
6
+ #
7
+ # Summer 2023, BRACE2 Team
8
+ # Berkeley, CA
9
+ #
10
+ #----------------------------------------------------------------------------#
11
+ import json
12
+
13
+ from django.forms.models import model_to_dict
14
+ from django.shortcuts import HttpResponse
15
+ from django.template import loader
16
+ from django.contrib.auth.decorators import login_required
17
+
18
+ from rest_framework import status
19
+ from rest_framework.decorators import api_view, permission_classes
20
+ from rest_framework.permissions import IsAuthenticated
21
+
22
+ from .models import Evaluation
23
+ from apps.events.models import Event
24
+ from apps.inventory import views as inventory
25
+ from apps.inventory.models import Asset
26
+
27
+ def _evals_and_events(asset):
28
+ evals = Evaluation.objects.filter(asset_id=asset.id)
29
+ events = (Event.objects.get(id=eval.event.id) for eval in evals)
30
+ return evals, events
31
+
32
+ @api_view(['GET'])
33
+ @permission_classes([IsAuthenticated])
34
+ def get_evaluations(request):
35
+ if request.user.is_anonymous:
36
+ return HttpResponse(json.dumps({"detail": "Not authorized"}),
37
+ status=status.HTTP_401_UNAUTHORIZED)
38
+
39
+ evals_data = Evaluation.objects.all()
40
+
41
+ evals_count = evals_data.count()
42
+
43
+ evals_by_bridge = {
44
+ bridge.cesmd: _evals_and_events(bridge)
45
+ for bridge in Asset.objects.all()
46
+ }
47
+
48
+ summary = [
49
+ {
50
+ "cesmd": cesmd,
51
+ "events": [
52
+ {
53
+ # "event_file": print(eval) or str(eval.event.event_file.path),
54
+ "motion_data": eval.event.motion_data,
55
+ "evaluation": model_to_dict(eval)
56
+ } for eval in evals
57
+ ],
58
+ "summary": {
59
+ "SPECTRAL_SHIFT_IDENTIFICATION": inventory._ssid_stats(
60
+ events, #[inventory._find_ssid(event_id=event.id) for event in events],
61
+ "period"
62
+ )
63
+ }
64
+ } for cesmd, (evals, events) in evals_by_bridge.items()
65
+ ]
66
+ return HttpResponse(json.dumps({"count": evals_count, "data": summary}),
67
+ status=status.HTTP_200_OK)
68
+
File without changes
@@ -0,0 +1,9 @@
1
+ #===----------------------------------------------------------------------===#
2
+ #
3
+ # STAIRLab -- STructural Artificial Intelligence Laboratory
4
+ #
5
+ #===----------------------------------------------------------------------===#
6
+ from django.contrib import admin
7
+ from .models import Event
8
+
9
+ admin.site.register(Event)
@@ -0,0 +1,12 @@
1
+ #===----------------------------------------------------------------------===#
2
+ #
3
+ # STAIRLab -- STructural Artificial Intelligence Laboratory
4
+ #
5
+ #===----------------------------------------------------------------------===#
6
+ from django.apps import AppConfig
7
+
8
+
9
+ class AppConfig(AppConfig):
10
+ name = "apps.events"
11
+ label= "apps_events"
12
+
@@ -0,0 +1,27 @@
1
+ # Generated by Django 3.2.13 on 2022-07-23 00:58
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ initial = True
9
+
10
+ dependencies = [
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='Event',
16
+ fields=[
17
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('upload_date', models.DateField()),
19
+ ('event_file', models.FileField(upload_to='events')),
20
+ ('upload_data', models.JSONField(default=dict)),
21
+ ('motion_data', models.JSONField(default=dict)),
22
+ ],
23
+ options={
24
+ 'ordering': ['-id'],
25
+ },
26
+ ),
27
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 3.2.13 on 2022-07-23 01:03
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('apps_events', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='event',
15
+ name='id',
16
+ field=models.BigAutoField(primary_key=True, serialize=False),
17
+ ),
18
+ ]
@@ -0,0 +1,19 @@
1
+ # Generated by Django 3.2.13 on 2022-07-24 03:06
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('apps_events', '0002_alter_event_id'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='event',
15
+ name='cesmd',
16
+ field=models.CharField(default='CE58658', max_length=7),
17
+ preserve_default=False,
18
+ ),
19
+ ]
@@ -0,0 +1,19 @@
1
+ # Generated by Django 3.2.13 on 2023-07-03 22:13
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('apps_events', '0003_event_cesmd'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='event',
15
+ name='record_identifier',
16
+ field=models.CharField(default='', max_length=40),
17
+ preserve_default=False,
18
+ ),
19
+ ]
@@ -0,0 +1,21 @@
1
+ # Generated by Django 3.2.13 on 2023-07-31 18:42
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('apps_inventory', '0009_auto_20230731_1842'),
11
+ ('apps_events', '0004_event_record_identifier'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='event',
17
+ name='asset',
18
+ field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='apps_inventory.asset'),
19
+ preserve_default=False,
20
+ ),
21
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 3.2.13 on 2023-08-07 19:48
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('apps_events', '0005_event_asset'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='event',
15
+ name='event_file',
16
+ field=models.FileField(blank=True, upload_to='events'),
17
+ ),
18
+ ]
File without changes
@@ -0,0 +1,70 @@
1
+ #===----------------------------------------------------------------------===#
2
+ #
3
+ # STAIRLab -- STructural Artificial Intelligence Laboratory
4
+ #
5
+ #===----------------------------------------------------------------------===#
6
+ #
7
+ # Author: Claudio Perez
8
+ #
9
+ #----------------------------------------------------------------------------#
10
+ import os
11
+ from django.db import models
12
+ from django.conf import settings
13
+ from django.core.files.storage import FileSystemStorage
14
+ from django.db.models.signals import post_delete, pre_save
15
+ from django.dispatch import receiver
16
+ from django.core.mail import send_mail
17
+
18
+ from apps.inventory.models import Asset
19
+
20
+
21
+ class Event(models.Model):
22
+ id = models.BigAutoField(primary_key=True)
23
+ upload_date = models.DateField(blank=False)
24
+ event_file = models.FileField(upload_to='events', blank=True)
25
+ upload_data = models.JSONField(default=dict)
26
+ motion_data = models.JSONField(default=dict)
27
+ cesmd = models.CharField(max_length=7)
28
+ record_identifier = models.CharField(max_length=40)
29
+
30
+ # TODO: add field indicating if event_file and/or series data is present
31
+
32
+ asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
33
+
34
+ def __str__(self):
35
+ return f"{self.cesmd} - {self.record_identifier}"
36
+
37
+ class Meta:
38
+ ordering = ["-id"]
39
+
40
+ def email_notify(self, subject, message, recipients, **kwds):
41
+ email_from = settings.EMAIL_HOST_USER
42
+ send_mail(subject, message, email_from, recipients, **kwds)
43
+
44
+ @property
45
+ def pga(self):
46
+ return abs(self.motion_data["peak_accel"])/980.
47
+
48
+
49
+ # Signal to delete the event file when the model instance is deleted
50
+ @receiver(post_delete, sender=Event)
51
+ def delete_file_on_delete(sender, instance, **kwargs):
52
+ if instance.event_file:
53
+ if os.path.isfile(instance.event_file.path):
54
+ os.remove(instance.event_file.path)
55
+
56
+ # Signal to delete the old event file when the file is replaced
57
+ @receiver(pre_save, sender=Event)
58
+ def delete_file_on_change(sender, instance, **kwargs):
59
+ if not instance.pk:
60
+ return False
61
+
62
+ try:
63
+ old_file = sender.objects.get(pk=instance.pk).event_file
64
+ except sender.DoesNotExist:
65
+ return False
66
+
67
+ new_file = instance.event_file
68
+ if old_file and old_file != new_file:
69
+ if os.path.isfile(old_file.path):
70
+ os.remove(old_file.path)
@@ -0,0 +1 @@
1
+ from django.test import TestCase