From ef5abb44747efb25379dd57483eb7d282244ae54 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 27 Jan 2017 17:52:53 +0100 Subject: Add external ids to Containers and Warehouses --- ishtar_common/models.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'ishtar_common/models.py') diff --git a/ishtar_common/models.py b/ishtar_common/models.py index d1d58f184..0da937f1a 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1104,6 +1104,20 @@ class IshtarSiteProfile(models.Model, Cached): u"Change this with care. With incorrect formula, the " u"application might be unusable and import of external " u"data can be destructive.")) + container_external_id = models.TextField( + _(u"Container external id"), + default="{responsible__external_id-index", + help_text=_(u"Formula to manage container external ID. " + u"Change this with care. With incorrect formula, the " + u"application might be unusable and import of external " + u"data can be destructive.")) + warehouse_external_id = models.TextField( + _(u"Warehouse external id"), + default="{name|slug}", + help_text=_(u"Formula to manage warehouse external ID. " + u"Change this with care. With incorrect formula, the " + u"application might be unusable and import of external " + u"data can be destructive.")) person_raw_name = models.TextField( _(u"Raw name for person"), default="{name|upper} {surname}", -- cgit v1.2.3 From 00d97bcabb8e248beced972fa2a890f480cee987 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 27 Jan 2017 18:05:44 +0100 Subject: Fix data migration for warehouse - fix default external id formula for containers --- .../0020_generate_cache_lbl_for_containers.py | 4 + .../0024_generate_cache_lbl_for_containers.py | 298 +++++++++++++++++++++ ishtar_common/models.py | 2 +- 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 archaeological_warehouse/migrations/0024_generate_cache_lbl_for_containers.py (limited to 'ishtar_common/models.py') diff --git a/archaeological_warehouse/migrations/0020_generate_cache_lbl_for_containers.py b/archaeological_warehouse/migrations/0020_generate_cache_lbl_for_containers.py index f19106835..4c6e08dbe 100644 --- a/archaeological_warehouse/migrations/0020_generate_cache_lbl_for_containers.py +++ b/archaeological_warehouse/migrations/0020_generate_cache_lbl_for_containers.py @@ -9,9 +9,13 @@ from archaeological_warehouse.models import Container class Migration(SchemaMigration): def forwards(self, orm): + # migration disabled + """ for obj in Container.objects.all(): obj.skip_history_when_saving = True obj.save() + """ + pass def backwards(self, orm): pass diff --git a/archaeological_warehouse/migrations/0024_generate_cache_lbl_for_containers.py b/archaeological_warehouse/migrations/0024_generate_cache_lbl_for_containers.py new file mode 100644 index 000000000..c0e97cca2 --- /dev/null +++ b/archaeological_warehouse/migrations/0024_generate_cache_lbl_for_containers.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models +from archaeological_warehouse.models import Container + + +class Migration(SchemaMigration): + + def forwards(self, orm): + for obj in Container.objects.all(): + obj.skip_history_when_saving = True + obj.save() + + def backwards(self, orm): + pass + + models = { + 'archaeological_warehouse.collection': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Collection'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'history_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_archaeological_warehouse_collection'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'warehouse': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'collections'", 'to': "orm['archaeological_warehouse.Warehouse']"}) + }, + 'archaeological_warehouse.container': { + 'Meta': {'ordering': "('cached_label',)", 'unique_together': "(('index', 'location'),)", 'object_name': 'Container'}, + 'auto_external_id': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'cached_label': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), + 'cached_location': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'container_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_warehouse.ContainerType']"}), + 'external_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'history_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_archaeological_warehouse_container'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), + 'index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'location': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'containers'", 'to': "orm['archaeological_warehouse.Warehouse']"}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'responsible': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_containers'", 'to': "orm['archaeological_warehouse.Warehouse']"}) + }, + 'archaeological_warehouse.containerlocalisation': { + 'Meta': {'ordering': "('container', 'division__order')", 'unique_together': "(('container', 'division'),)", 'object_name': 'ContainerLocalisation'}, + 'container': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_warehouse.Container']"}), + 'division': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_warehouse.WarehouseDivisionLink']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200'}) + }, + 'archaeological_warehouse.containertype': { + 'Meta': {'ordering': "('label',)", 'object_name': 'ContainerType'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'volume': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'archaeological_warehouse.warehouse': { + 'Meta': {'object_name': 'Warehouse'}, + 'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address_is_prefered': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'alt_country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'alt_postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'alt_town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}), + 'associated_divisions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['archaeological_warehouse.WarehouseDivision']", 'symmetrical': 'False', 'through': "orm['archaeological_warehouse.WarehouseDivisionLink']", 'blank': 'True'}), + 'auto_external_id': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'external_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_archaeological_warehouse_warehouse'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), + 'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'person_in_charge': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'warehouse_in_charge'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['ishtar_common.Person']"}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone2': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone3': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone_desc': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'phone_desc2': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'phone_desc3': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'raw_phone': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}), + 'warehouse_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_warehouse.WarehouseType']"}) + }, + 'archaeological_warehouse.warehousedivision': { + 'Meta': {'object_name': 'WarehouseDivision'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'archaeological_warehouse.warehousedivisionlink': { + 'Meta': {'ordering': "('warehouse', 'order')", 'unique_together': "(('warehouse', 'division'),)", 'object_name': 'WarehouseDivisionLink'}, + 'division': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_warehouse.WarehouseDivision']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'warehouse': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archaeological_warehouse.Warehouse']"}) + }, + 'archaeological_warehouse.warehousetype': { + 'Meta': {'ordering': "('label',)", 'object_name': 'WarehouseType'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ishtar_common.import': { + 'Meta': {'object_name': 'Import'}, + 'conservative_import': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), + 'encoding': ('django.db.models.fields.CharField', [], {'default': "'utf-8'", 'max_length': '15'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'error_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imported_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'imported_images': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.ImporterType']"}), + 'match_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'result_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'seconds_remaining': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'skip_lines': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'C'", 'max_length': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.IshtarUser']"}) + }, + 'ishtar_common.importertype': { + 'Meta': {'object_name': 'ImporterType'}, + 'associated_models': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_template': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'unicity_keys': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ishtar_common.IshtarUser']", 'null': 'True', 'blank': 'True'}) + }, + 'ishtar_common.ishtaruser': { + 'Meta': {'object_name': 'IshtarUser', '_ormbases': ['auth.User']}, + 'advanced_shortcut_menu': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ishtaruser'", 'unique': 'True', 'to': "orm['ishtar_common.Person']"}), + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'ishtar_common.organization': { + 'Meta': {'object_name': 'Organization'}, + 'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address_is_prefered': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'alt_country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'alt_postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'alt_town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}), + 'archived': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_ishtar_common_organization'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), + 'merge_candidate': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_candidate_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Organization']"}), + 'merge_exclusion': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_exclusion_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Organization']"}), + 'merge_key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500'}), + 'organization_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.OrganizationType']"}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone2': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone3': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone_desc': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'phone_desc2': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'phone_desc3': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'raw_phone': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) + }, + 'ishtar_common.organizationtype': { + 'Meta': {'ordering': "('label',)", 'object_name': 'OrganizationType'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'ishtar_common.person': { + 'Meta': {'object_name': 'Person'}, + 'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'alt_address_is_prefered': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'alt_country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'alt_postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'alt_town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}), + 'archived': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 'attached_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'members'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['ishtar_common.Organization']"}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'contact_type': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_ishtar_common_person'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), + 'merge_candidate': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_candidate_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Person']"}), + 'merge_exclusion': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_exclusion_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Person']"}), + 'merge_key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'old_title': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'person_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ishtar_common.PersonType']", 'symmetrical': 'False'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone2': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone3': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), + 'phone_desc': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'phone_desc2': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'phone_desc3': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'raw_name': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), + 'raw_phone': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'salutation': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'surname': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.TitleType']", 'null': 'True', 'blank': 'True'}), + 'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) + }, + 'ishtar_common.persontype': { + 'Meta': {'ordering': "('label',)", 'object_name': 'PersonType'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'ishtar_common.titletype': { + 'Meta': {'ordering': "('label',)", 'object_name': 'TitleType'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + } + } + + complete_apps = ['archaeological_warehouse'] \ No newline at end of file diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 0da937f1a..604c6923c 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1106,7 +1106,7 @@ class IshtarSiteProfile(models.Model, Cached): u"data can be destructive.")) container_external_id = models.TextField( _(u"Container external id"), - default="{responsible__external_id-index", + default="{responsible__external_id}-{index}", help_text=_(u"Formula to manage container external ID. " u"Change this with care. With incorrect formula, the " u"application might be unusable and import of external " -- cgit v1.2.3 From 019f483daeb9fca526ef5f46fda650f01c551fc0 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 27 Jan 2017 18:23:42 +0100 Subject: Importers: add "container type" to available types for type columns --- ishtar_common/models.py | 1 + 1 file changed, 1 insertion(+) (limited to 'ishtar_common/models.py') diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 604c6923c..6fb7852ae 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -2061,6 +2061,7 @@ TARGET_MODELS = [ ('archaeological_finds.models.MaterialType', _(u"Material")), ('archaeological_finds.models.ConservatoryState', _(u"Conservatory state")), + ('archaeological_warehouse.models.ContainerType', _(u"Container type")), ('archaeological_finds.models.PreservationType', _(u"Preservation type")), ('archaeological_finds.models.ObjectType', _(u"Object type")), ('archaeological_finds.models.IntegrityType', _(u"Integrity type")), -- cgit v1.2.3 From 478befbbf1705b8547f8e88a4b5822d3bad52200 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Sun, 29 Jan 2017 17:33:45 +0100 Subject: Fix performance issues for shortcut menu --- archaeological_context_records/models.py | 8 ++-- archaeological_files/models.py | 21 ++++++---- archaeological_finds/models_finds.py | 11 +++--- archaeological_finds/models_treatments.py | 21 +++++----- archaeological_operations/models.py | 11 +++--- ishtar_common/models.py | 43 ++++++++++++++++++--- ishtar_common/tests.py | 64 +++++++++++++++++++++++++++++++ ishtar_common/views.py | 28 ++++++++------ 8 files changed, 158 insertions(+), 49 deletions(-) (limited to 'ishtar_common/models.py') diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index 518610aa5..cd0dd4268 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -269,14 +269,16 @@ class ContextRecord(BaseHistorizedItem, ImageModel, OwnPerms, Q(history_creator=user) @classmethod - def get_owns(cls, user, menu_filtr=None, limit=None): + def get_owns(cls, user, menu_filtr=None, limit=None, + values=None, get_short_menu_class=None): replace_query = None if menu_filtr and 'operation' in menu_filtr: replace_query = Q(operation=menu_filtr['operation']) owns = super(ContextRecord, cls).get_owns( user, replace_query=replace_query, - limit=limit) - return sorted(owns, key=lambda x: x.cached_label) + limit=limit, values=values, + get_short_menu_class=get_short_menu_class) + return cls._return_get_owns(owns, values, get_short_menu_class) def full_label(self): return unicode(self) diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 638b19d18..15e65579c 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -340,12 +340,16 @@ class File(ClosedItem, BaseHistorizedItem, OwnPerms, ValueGetter, cache.set(cache_key, has_adminact, settings.CACHE_TIMEOUT) return has_adminact - def get_short_menu_class(self): - cache_key, val = get_cache(self.__class__, [self.pk, - 'short_class_name']) + @classmethod + def get_short_menu_class(cls, pk): + cache_key, val = get_cache(cls, [pk, 'short_class_name']) if val: return val - return self.update_short_menu_class(cache_key) + q = cls.objects.filter(pk=pk) + if not q.count(): + return '' + item = q.all()[0] + return item.update_short_menu_class(cache_key) def update_short_menu_class(self, cache_key=None): if not cache_key: @@ -366,9 +370,12 @@ class File(ClosedItem, BaseHistorizedItem, OwnPerms, ValueGetter, return cls @classmethod - def get_owns(cls, user, menu_filtr=None, limit=None): - owns = super(File, cls).get_owns(user, limit=limit) - return sorted(owns, key=lambda x: x.cached_label) + def get_owns(cls, user, menu_filtr=None, limit=None, values=None, + get_short_menu_class=False): + owns = super(File, cls).get_owns( + user, limit=limit, values=values, + get_short_menu_class=get_short_menu_class) + return cls._return_get_owns(owns, values, get_short_menu_class) def get_values(self, prefix=''): values = super(File, self).get_values(prefix=prefix) diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index e8b6135a8..588edb5cf 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -687,18 +687,17 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): Q(history_creator=user) @classmethod - def get_owns(cls, user, menu_filtr=None, limit=None): + def get_owns(cls, user, menu_filtr=None, limit=None, + values=None, get_short_menu_class=None): replace_query = None if menu_filtr and 'contextrecord' in menu_filtr: replace_query = Q( base_finds__context_record=menu_filtr['contextrecord'] ) owns = super(Find, cls).get_owns( - user, replace_query=replace_query, - limit=limit) - return sorted( - owns, key=lambda x: x.cached_label - if hasattr(x, 'cached_label') else unicode(x)) + user, replace_query=replace_query, limit=limit, values=values, + get_short_menu_class=get_short_menu_class) + return cls._return_get_owns(owns, values, get_short_menu_class) def _generate_cached_label(self): return unicode(self) diff --git a/archaeological_finds/models_treatments.py b/archaeological_finds/models_treatments.py index d8d51e28c..e94d1c272 100644 --- a/archaeological_finds/models_treatments.py +++ b/archaeological_finds/models_treatments.py @@ -171,7 +171,8 @@ class Treatment(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): & Q(end_date__isnull=True) @classmethod - def get_owns(cls, user, menu_filtr=None, limit=None): + def get_owns(cls, user, menu_filtr=None, limit=None, values=None, + get_short_menu_class=None): replace_query = None if menu_filtr: if 'treatmentfile' in menu_filtr: @@ -184,10 +185,9 @@ class Treatment(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): else: replace_query = q owns = super(Treatment, cls).get_owns( - user, replace_query=replace_query, limit=limit) - return sorted( - owns, key=lambda x: x.cached_label - if hasattr(x, 'cached_label') else unicode(x)) + user, replace_query=replace_query, limit=limit, + values=values, get_short_menu_class=get_short_menu_class) + return cls._return_get_owns(owns, values, get_short_menu_class) def get_query_operations(self): return Operation.objects.filter( @@ -531,11 +531,12 @@ class TreatmentFile(ClosedItem, BaseHistorizedItem, OwnPerms, ValueGetter, 'name') if getattr(self, attr)]) @classmethod - def get_owns(cls, user, menu_filtr=None, limit=None): - owns = super(TreatmentFile, cls).get_owns(user, limit=limit) - return sorted( - owns, key=lambda x: x.cached_label - if hasattr(x, 'cached_label') else unicode(x)) + def get_owns(cls, user, menu_filtr=None, limit=None, values=None, + get_short_menu_class=None): + owns = super(TreatmentFile, cls).get_owns( + user, limit=limit, values=values, + get_short_menu_class=get_short_menu_class) + return cls._return_get_owns(owns, values, get_short_menu_class) def _generate_cached_label(self): items = [unicode(getattr(self, k)) diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index 4a900c276..30cd61010 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -379,16 +379,17 @@ class Operation(ClosedItem, BaseHistorizedItem, ImageModel, OwnPerms, ordering = ('cached_label',) @classmethod - def get_owns(cls, user, menu_filtr=None, limit=None): + def get_owns(cls, user, menu_filtr=None, limit=None, values=None, + get_short_menu_class=None): replace_query = None if menu_filtr and 'file' in menu_filtr: replace_query = Q(associated_file=menu_filtr['file']) + owns = super(Operation, cls).get_owns( user, replace_query=replace_query, - limit=limit) - # owns = owns.annotate(null_count=Count('operation_code')) - # return owns.order_by("common_name", "-year", "operation_code") - return sorted(owns, key=lambda x: x.cached_label) + limit=limit, values=values, + get_short_menu_class=get_short_menu_class) + return cls._return_get_owns(owns, values, get_short_menu_class) def __unicode__(self): if self.cached_label: diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 6fb7852ae..7015c70a5 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -226,7 +226,7 @@ class OwnPerms: query = self.get_query_owns(user) if not query: return False - query = query & Q(pk=self.pk) + query &= Q(pk=self.pk) return self.__class__.objects.filter(query).count() @classmethod @@ -240,28 +240,57 @@ class OwnPerms: return cls.objects.filter(query).count() @classmethod - def get_owns(cls, user, replace_query={}, limit=None): + def _return_get_owns(cls, owns, values, get_short_menu_class, + label_key='cached_label'): + if not values: + if not get_short_menu_class: + return sorted(owns, key=lambda x: getattr(x, label_key)) + return sorted(owns, key=lambda x: getattr(x[0], label_key)) + if not get_short_menu_class: + return sorted(owns, key=lambda x: x[label_key]) + return sorted(owns, key=lambda x: x[0][label_key]) + + @classmethod + def get_owns(cls, user, replace_query={}, limit=None, values=None, + get_short_menu_class=False): """ Get Own items """ if isinstance(user, User): user = IshtarUser.objects.get(user_ptr=user) if user.is_anonymous(): - return cls.objects.filter(pk__isnull=True) + returned = cls.objects.filter(pk__isnull=True) + if values: + returned = [] + return returned items = [] if hasattr(cls, 'BASKET_MODEL'): items = list(cls.BASKET_MODEL.objects.filter(user=user).all()) query = cls.get_query_owns(user) if not query and not replace_query: - return cls.objects.filter(pk__isnull=True) + returned = cls.objects.filter(pk__isnull=True) + if values: + returned = [] + return returned if query: q = cls.objects.filter(query) if replace_query: q = cls.objects.filter(replace_query) + if values: + q = q.values(*values) if limit: items += list(q.order_by('-pk')[:limit]) else: items += list(q.order_by(*cls._meta.ordering).all()) + if get_short_menu_class: + if values: + if 'id' not in values: + raise NotImplementedError( + "Call of get_owns with get_short_menu_class option and" + " no 'id' in values is not implemented") + items = [(i, cls.get_short_menu_class(i['id'])) for i in items] + else: + items = [(i, cls.get_short_menu_class(i.pk)) for i in items] return items @@ -643,7 +672,8 @@ class Basket(models.Model): def __unicode__(self): return self.label - def get_short_menu_class(self): + @classmethod + def get_short_menu_class(cls, pk): return 'basket' @property @@ -968,7 +998,8 @@ def post_delete_record_relation(sender, instance, **kwargs): class ShortMenuItem(object): - def get_short_menu_class(self): + @classmethod + def get_short_menu_class(cls, pk): return '' diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py index 10584e4f2..cdf6ce330 100644 --- a/ishtar_common/tests.py +++ b/ishtar_common/tests.py @@ -334,6 +334,70 @@ class MergeTest(TestCase): init_mc) +class ShortMenuTest(TestCase): + def setUp(self): + from archaeological_operations.models import OperationType + self.username = 'username666' + self.password = 'dcbqj7xnjkxnjsknx!@%' + self.user = User.objects.create_superuser( + self.username, "nomail@nomail.com", self.password) + self.other_user = User.objects.create_superuser( + 'John', "nomail@nomail.com", self.password) + self.ope_type = OperationType.objects.create() + + def testNotConnected(self): + c = Client() + response = c.get(reverse('shortcut-menu')) + # no content if not logged + self.assertFalse("shortcut-menu" in response.content) + c = Client() + c.login(username=self.username, password=self.password) + # no content because the user owns no object + response = c.get(reverse('shortcut-menu')) + self.assertFalse("shortcut-menu" in response.content) + from archaeological_operations.models import Operation + Operation.objects.create( + operation_type=self.ope_type, + history_modifier=self.user) + # content is here + response = c.get(reverse('shortcut-menu')) + self.assertTrue("shortcut-menu" in response.content) + + def testOperation(self): + from archaeological_operations.models import Operation + c = Client() + c.login(username=self.username, password=self.password) + ope = Operation.objects.create( + operation_type=self.ope_type, + history_modifier=self.other_user, + year=2042, operation_code=54 + ) + # not available at first + response = c.get(reverse('shortcut-menu')) + self.assertFalse(str(ope.cached_label) in response.content) + + # available because is the creator + ope.history_creator = self.user + ope.save() + response = c.get(reverse('shortcut-menu')) + self.assertTrue(str(ope.cached_label) in response.content) + + # available because is in charge + ope.history_creator = self.other_user + ope.in_charge = self.user.ishtaruser.person + ope.save() + response = c.get(reverse('shortcut-menu')) + self.assertTrue(str(ope.cached_label) in response.content) + + # available because is the scientist + ope.history_creator = self.other_user + ope.in_charge = None + ope.scientist = self.user.ishtaruser.person + ope.save() + response = c.get(reverse('shortcut-menu')) + self.assertTrue(str(ope.cached_label) in response.content) + + class ImportTest(TestCase): def testDeleteRelated(self): town = models.Town.objects.create(name='my-test') diff --git a/ishtar_common/views.py b/ishtar_common/views.py index b0817fc59..dbbc3d538 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -256,7 +256,6 @@ def shortcut_menu(request): model_name = model.SLUG current = model_name in request.session \ and request.session[model_name] - dct['menu'].append(( lbl, model_name, current or 0, JQueryAutoComplete( @@ -275,23 +274,28 @@ def shortcut_menu(request): cls = '' current = model_name in request.session and request.session[model_name] items = [] - for item in model.get_owns(request.user, - menu_filtr=current_selected_item, - limit=100): - pk = unicode(item.pk) - if item.IS_BASKET: + current_items = [] + for item, shortmenu_class in model.get_owns( + request.user, menu_filtr=current_selected_item, limit=100, + values=['id', 'cached_label'], get_short_menu_class=True): + pk = unicode(item['id']) + if shortmenu_class == 'basket': pk = "basket-" + pk + # prevent duplicates + if pk in current_items: + continue + current_items.append(pk) selected = pk == current if selected: - cls = item.get_short_menu_class() - new_selected_item = item - items.append((pk, shortify(unicode(item), 60), - selected, item.get_short_menu_class())) + cls = shortmenu_class + new_selected_item = pk + items.append((pk, shortify(item['cached_label'], 60), + selected, shortmenu_class)) # selected is not in owns - add it to the list if not new_selected_item and current: try: item = model.objects.get(pk=int(current)) - new_selected_item = item + new_selected_item = item.pk items.append((item.pk, shortify(unicode(item), 60), True, item.get_short_menu_class())) except (model.DoesNotExist, ValueError): @@ -323,7 +327,7 @@ def get_current_items(request): def unpin(request, item_type): if item_type not in ('find', 'contextrecord', 'operation', 'file', - 'treatment', 'treatmentfile'): + 'treatment', 'treatmentfile'): logger.warning("unpin unknow type: {}".format(item_type)) return HttpResponse('nok') request.session['treatment'] = '' -- cgit v1.2.3