From b8a79b12a42cc2ede5799c5f39cdd1625319acb7 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Tue, 15 Apr 2014 17:11:28 +0200 Subject: [PATCH 01/10] Log object creation/update/deletion in django admin log --- mapentity/models.py | 28 ++++++++++++++++++++++++++++ mapentity/views/generic.py | 24 +++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/mapentity/models.py b/mapentity/models.py index 9fe1a4e60..eeb2c7b64 100755 --- a/mapentity/models.py +++ b/mapentity/models.py @@ -2,8 +2,14 @@ from django.db import models from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError from django.contrib import auth +from django.contrib.admin.models import LogEntry, ADDITION +from django.contrib.contenttypes.models import ContentType + + + from paperclip.models import Attachment @@ -211,3 +217,25 @@ def get_delete_url(self): def get_attributes_html(self, rooturl): url = smart_urljoin(rooturl, self.get_detail_url()) return extract_attributes_html(url) + + @classmethod + def get_content_type_id(cls): + return ContentType.objects.get_for_model(cls).pk + + @property + def creator(self): + log_entry = LogEntry.objects.get( + content_type_id=self.get_content_type_id(), + object_id=self.pk, + action_flag=ADDITION) + return log_entry.user + + @property + def authors(self): + return auth.get_user_model().objects.filter( + logentry__content_type_id=self.get_content_type_id(), + logentry__object_id=self.pk) + + @property + def last_author(self): + return self.authors.order_by('logentry__pk').last() diff --git a/mapentity/views/generic.py b/mapentity/views/generic.py index 825d60b99..31c6e4308 100755 --- a/mapentity/views/generic.py +++ b/mapentity/views/generic.py @@ -6,6 +6,7 @@ HttpResponseServerError) from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_text from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.list import ListView @@ -13,6 +14,7 @@ from django.template.base import TemplateDoesNotExist from django.template.defaultfilters import slugify from django.contrib import messages +from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from djgeojson.views import GeoJSONLayerView from djappypod.odt import get_template from djappypod.response import OdtTemplateResponse @@ -31,6 +33,16 @@ logger = logging.getLogger(__name__) +def log_action(request, object, action_flag): + LogEntry.objects.log_action( + user_id=request.user.pk, + content_type_id=object.get_content_type_id(), + object_id=object.pk, + object_repr=force_text(object), + action_flag=action_flag + ) + + class MapEntityLayer(ModelViewMixin, GeoJSONLayerView): """ Take a class attribute `model` with a `latest_updated` method used for caching. @@ -347,8 +359,10 @@ def get_form_kwargs(self): return kwargs def form_valid(self, form): + response = super(MapEntityCreate, self).form_valid(form) messages.success(self.request, _("Created")) - return super(MapEntityCreate, self).form_valid(form) + log_action(self.request, self.object, ADDITION) + return response def form_invalid(self, form): messages.error(self.request, _("Your form contains errors")) @@ -415,8 +429,10 @@ def get_form_kwargs(self): return kwargs def form_valid(self, form): + response = super(MapEntityUpdate, self).form_valid(form) messages.success(self.request, _("Saved")) - return super(MapEntityUpdate, self).form_valid(form) + log_action(self.request, self.object, CHANGE) + return response def form_invalid(self, form): messages.error(self.request, _("Your form contains errors")) @@ -439,8 +455,10 @@ def dispatch(self, *args, **kwargs): return super(MapEntityDelete, self).dispatch(*args, **kwargs) def delete(self, request, *args, **kwargs): + self.object = self.get_object() + log_action(self.request, self.object, DELETION) # Remove entry from history - history_delete(request, path=self.get_object().get_detail_url()) + history_delete(request, path=self.object.get_detail_url()) return super(MapEntityDelete, self).delete(request, *args, **kwargs) def get_success_url(self): From d9e995ec1f102335bcb621393b78b8df696a9b11 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Wed, 16 Apr 2014 14:33:45 +0200 Subject: [PATCH 02/10] Add a view to list actions history --- mapentity/__init__.py | 2 + mapentity/filters.py | 16 +- mapentity/locale/fr/LC_MESSAGES/django.mo | Bin 2761 -> 2482 bytes mapentity/locale/fr/LC_MESSAGES/django.po | 210 +++++++++--------- mapentity/models.py | 35 ++- mapentity/templates/mapentity/base.html | 2 +- .../templates/mapentity/logentry_list.html | 1 + mapentity/urls.py | 4 + mapentity/views/__init__.py | 13 +- mapentity/views/logentry.py | 44 ++++ 10 files changed, 205 insertions(+), 122 deletions(-) create mode 100644 mapentity/templates/mapentity/logentry_list.html create mode 100644 mapentity/views/logentry.py diff --git a/mapentity/__init__.py b/mapentity/__init__.py index 1fda6b47b..207ee95bf 100755 --- a/mapentity/__init__.py +++ b/mapentity/__init__.py @@ -121,6 +121,7 @@ class Registry(object): def __init__(self): self.registry = OrderedDict() self.apps = {} + self.content_type_ids = [] def register(self, model, name='', menu=True): """ Register model and returns URL patterns @@ -165,6 +166,7 @@ def register(self, model, name='', menu=True): self.registry[model] = mapentity post_register.send(sender=self, app_label=app_label, model=model) + self.content_type_ids.append(model.get_content_type_id()) # Returns Django URL patterns return patterns(name, *view_classes_to_url(*picked)) diff --git a/mapentity/filters.py b/mapentity/filters.py index d17feb426..b99e85377 100644 --- a/mapentity/filters.py +++ b/mapentity/filters.py @@ -86,14 +86,9 @@ def filter(self, qs, value): return qs.filter(pk__in=filtered) -class MapEntityFilterSet(FilterSet): - bbox = PolygonFilter() - - class Meta: - fields = ['bbox'] - +class BaseMapEntityFilterSet(FilterSet): def __init__(self, *args, **kwargs): - super(MapEntityFilterSet, self).__init__(*args, **kwargs) + super(BaseMapEntityFilterSet, self).__init__(*args, **kwargs) self.__bypass_labels() def __bypass_labels(self): @@ -123,3 +118,10 @@ def __set_placeholder(self, field, widget): widget.attrs['data-placeholder'] = field.label widget.attrs['title'] = field.label widget.attrs['data-label'] = field.label + + +class MapEntityFilterSet(BaseMapEntityFilterSet): + bbox = PolygonFilter() + + class Meta: + fields = ['bbox'] diff --git a/mapentity/locale/fr/LC_MESSAGES/django.mo b/mapentity/locale/fr/LC_MESSAGES/django.mo index a61bee58b3bb3d86653a6e526806516fee037c30..fdf19789b8a3150081a148dce07f15d55caedcbc 100644 GIT binary patch delta 1116 zcmX|=O-NKx6vwYQAC6htOlluiV`*cSFv1{dQ-SsYMM6aohJ^KAIbk_XV~c`@ga~YT z6C$XfO%3jH)1pN%xNy-z2to)UXwjxcfo`IY-+$)j;hy_D=e+ywIrrT6<{RI{3QN(z zv_U%27Idu2m;>|@=O^XJ_250GI#y7;1te}PK)4aVRP*b0MG)d&+X zKz?%&LzVYC9)q>`C!kJp4sL*#Aj9ODsNCFv>dbxTKY?rUU%<_9#yy{dI^buRp}SvU z1O6bvDEZArjEyh{wc$gk#Lpqa%rL3qS?A9~_Am=jg?xf)>5_Z?9qQ!E?)e|bAi)Un zI>#nhNE3)-=)}X2wPqA5LDu=Vp$fVO8Rij_3VsX|un6OD0W!=_CUx|e<6o%vtI4ho zH9%cSON9O_kR+f2(l82-Lv1t!b+XHFC(J=z#Wd9VSCEdD+pUBWt=nC;D#_x`-U9Wm zz5%sg(ysewk5y^L(#AW`&edq;TI8x2iLOIZkM2R+Q2ibMMW@^D9Q`fZP#s8JRp)eV z>PWPMX&tJ+O|6$N*@bQ`Ta~XnVOs-Ft3BVdZv%<8(UBWhea{T!{E@ux;F%--n2%e5 zXMYDyS}&NgFM|ViCD>yRg_`Y9=vhOmE1Bx*?ujRR`?~kP;=U*BfWMqa-k|`mm~f5Ol{2GtUdn^ Dz|(ux delta 1409 zcmY+DO=w+36vwCby?nHZNm{Gvhs{g+VQW*HXv7*qF?|+&y68tD)#4(TmuWKk-o44> z-XLzGLXqM^$|bZEskpIuQVuIokIb)uM`(ZbH11j!=Y&@UwYp5klunk^= zdH5@Af!E>Fa2Yni+pyV~1#^c^ClmLx7mwjE4RqzOu;P)VpIYYCN{3f8Y6;`1Fzl7T03Upk@_fQq;BU z;~}U7BiZ|-+59_@rRFr0(1~n5fiE!r0v6y8t<+zEe=wmd_%q{Q8CRg*-_7_pY-jur z+z6#kciaXwEXr|y3{{yZ*)vP1Z8ToviX|8Ekh zZ)ICHc>wCJbsGJi`i*ztvge+laS&afK0Z}Nb`Wp;)*AHS`oLCAccyz?wWsKGrQfZ; zoQ~#p@ANi^?W8Ft-rF!Pl)a0scY%4+R%~q3^SO7om%_6?n2UWAky zt=y|kXI+JvG?#y?<9NxO2;ymT!bcz3a-5d(hr7nyR2WQIGq$=z`dPj^H}37n>Ban& z&Q}f&4Dl2O2Zsj-4h+2f^RcFL>(ZOeyZVa0UaQwk5lQtWZ|4f->B!BwIWv~b%y?HN zXHUz?t)ta?O$n+IG?`Xdgeb5*1xL& diff --git a/mapentity/locale/fr/LC_MESSAGES/django.po b/mapentity/locale/fr/LC_MESSAGES/django.po index 48bcfe834..85018c396 100644 --- a/mapentity/locale/fr/LC_MESSAGES/django.po +++ b/mapentity/locale/fr/LC_MESSAGES/django.po @@ -3,12 +3,11 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-04-04 11:10+0200\n" +"POT-Creation-Date: 2014-04-16 09:18+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,184 +17,129 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" -#: filters.py:20 +#: filters.py:21 msgid "Any year" msgstr "Année" -#: forms.py:36 +#: forms.py:138 +msgid "Create" +msgstr "Créer" + +#: forms.py:138 msgid "Save changes" msgstr "Sauvegarder" -#: forms.py:37 +#: forms.py:139 msgid "Cancel" msgstr "Annuler" -#: forms.py:46 +#: forms.py:149 msgid "Delete" msgstr "Supprimer" -#: serializers.py:37 serializers.py:39 +#: models.py:200 +msgid "Added" +msgstr "Ajouté" + +#: models.py:201 +msgid "Changed" +msgstr "Modifié" + +#: models.py:202 +msgid "Deleted" +msgstr "Supprimé" + +#: serializers/datatables.py:20 serializers/datatables.py:22 #: templates/mapentity/entity_detail_valuelist_fragment.html:7 msgid "None" msgstr "Aucun(e)" -#: serializers.py:144 +#: serializers/gpx.py:41 msgid "Modified" msgstr "Modifié" -#: tests.py:46 -msgid "Topology is not valid." -msgstr "Topologie invalide" - -#: views.py:488 -#, python-format -msgid "Add a new %s" -msgstr "Ajouter un nouveau %s" - -#: views.py:500 -msgid "Created" -msgstr "Créé" - -#: views.py:504 views.py:534 -msgid "Your form contains errors" -msgstr "Le formulaire contient des erreurs" - -#: views.py:518 -#, python-format -msgid "Edit %s" -msgstr "Éditer %s" - -#: views.py:530 -msgid "Saved" -msgstr "Sauvegardé" - -#: templates/mapentity/base.html:61 +#: templates/mapentity/base.html:66 msgid "List" msgstr "Liste" -#: templates/mapentity/base.html:66 templates/mapentity/entity_list.html:15 -msgid "Path" -msgstr "Tronçon" - -#: templates/mapentity/base.html:67 templates/mapentity/entity_list.html:16 -msgid "Intervention" -msgstr "Intervention" - -#: templates/mapentity/base.html:68 templates/mapentity/entity_list.html:17 -msgid "Project" -msgstr "Chantier" - -#: templates/mapentity/base.html:69 templates/mapentity/entity_list.html:18 -msgid "Signage" -msgstr "Signalétique" - -#: templates/mapentity/base.html:70 templates/mapentity/entity_list.html:19 -msgid "Infrastructure" -msgstr "Aménagement" - -#: templates/mapentity/base.html:71 templates/mapentity/entity_list.html:20 -msgid "Trek" -msgstr "Itinéraire" - -#: templates/mapentity/base.html:72 templates/mapentity/entity_list.html:21 -msgid "POI" -msgstr "POI" - -#: templates/mapentity/base.html:109 +#: templates/mapentity/base.html:117 msgid "Admin" msgstr "Admin" -#: templates/mapentity/base.html:112 +#: templates/mapentity/base.html:120 msgid "Logbook" msgstr "Journal" -#: templates/mapentity/base.html:115 +#: templates/mapentity/base.html:123 msgid "Logout" msgstr "Déconnexion" -#: templates/mapentity/entity_confirm_delete.html:18 +#: templates/mapentity/entity_confirm_delete.html:17 #, python-format msgid "Do you really wish to delete %(object)s ?" msgstr "Voulez-vous vraiment supprimer %(object)s ?" -#: templates/mapentity/entity_confirm_delete.html:22 +#: templates/mapentity/entity_confirm_delete.html:21 msgid "Yes, delete" msgstr "Oui, supprimer" -#: templates/mapentity/entity_confirm_delete.html:23 +#: templates/mapentity/entity_confirm_delete.html:22 msgid "No, back to edit view" msgstr "Non, retour à la vue d'édition" -#: templates/mapentity/entity_detail.html:12 +#: templates/mapentity/entity_detail.html:11 msgid "ODT" msgstr "ODT" #: templates/mapentity/entity_detail.html:13 -msgid "DOC" -msgstr "" - -#: templates/mapentity/entity_detail.html:14 msgid "PDF" msgstr "PDF" -#: templates/mapentity/entity_detail.html:22 +#: templates/mapentity/entity_detail.html:21 msgid "Properties" -msgstr "Priopriétés" - -#: templates/mapentity/entity_detail.html:24 -msgid "Structure" -msgstr "Aménagement" +msgstr "Propriétés" -#: templates/mapentity/entity_detail.html:29 -#: templates/mapentity/entity_detail.html:46 +#: templates/mapentity/entity_detail.html:26 +#: templates/mapentity/entity_detail.html:49 msgid "Attached files" msgstr "Fichiers liés" -#: templates/mapentity/entity_detail.html:32 +#: templates/mapentity/entity_detail.html:29 +#: templates/mapentity/entity_detail.html:31 msgid "Update" msgstr "Modifier" -#: templates/mapentity/entity_detail.html:54 +#: templates/mapentity/entity_detail.html:57 msgid "New file attachment" msgstr "Nouveau fichier lié" -#: templates/mapentity/entity_detail.html:69 +#: templates/mapentity/entity_detail.html:71 msgid "No map available for this object." msgstr "Pas de carte disponible pour cet objet." -#: templates/mapentity/entity_list.html:51 -msgid "CSV" -msgstr "" - -#: templates/mapentity/entity_list.html:52 -msgid "Shapefile" -msgstr "" - -#: templates/mapentity/entity_list.html:53 -msgid "GPX" -msgstr "" +#: templates/mapentity/entity_list.html:30 +#: templates/mapentity/entity_list.html:32 +#: templates/mapentity/popupplus.html:8 +msgid "Add" +msgstr "Ajouter" -#: templates/mapentity/entity_list.html:107 +#: templates/mapentity/entity_list.html:115 msgid "Save map as image" msgstr "Sauvegarder l'image" -#: templates/mapentity/entity_list.html:163 +#: templates/mapentity/entity_list.html:180 msgid "Search" msgstr "Rechercher" -#: templates/mapentity/entity_list.html:187 +#: templates/mapentity/entity_list.html:202 msgid "results" msgstr "résultats" #: templates/mapentity/entity_list_filter_fragment.html:3 -#: templates/mapentity/entity_list_filter_fragment.html:13 +#: templates/mapentity/entity_list_filter_fragment.html:17 msgid "Filter" msgstr "Filtrer" -#: templates/mapentity/popupplus.html:8 -msgid "Add" -msgstr "Ajouter" - #: templatetags/timesince.py:21 #, python-format msgid "%d year ago" @@ -235,14 +179,64 @@ msgstr[1] "%d minutes" msgid "just a few seconds ago" msgstr "quelques secondes" +#: tests/test_functional.py:48 +msgid "Topology is not valid." +msgstr "Topologie invalide" + +#: views/generic.py:334 +#, python-format +msgid "Add a new %s" +msgstr "Ajouter un nouveau %s" + +#: views/generic.py:347 +msgid "Created" +msgstr "Créé" + +#: views/generic.py:352 views/generic.py:413 +msgid "Your form contains errors" +msgstr "Le formulaire contient des erreurs" + +#: views/generic.py:391 +#, python-format +msgid "Edit %s" +msgstr "Éditer %s" + +#: views/generic.py:408 +msgid "Saved" +msgstr "Sauvegardé" + +#~ msgid "Path" +#~ msgstr "Tronçon" + +#~ msgid "Intervention" +#~ msgstr "Intervention" + +#~ msgid "Project" +#~ msgstr "Chantier" + +#~ msgid "Signage" +#~ msgstr "Signalétique" + +#~ msgid "Infrastructure" +#~ msgstr "Aménagement" + +#~ msgid "Trek" +#~ msgstr "Itinéraire" + +#~ msgid "POI" +#~ msgstr "POI" + +#~ msgid "Structure" +#~ msgstr "Aménagement" + #~ msgid "Unknown serialization format '%s'" #~ msgstr "Format de sérialization inconnnu '%s'" #~ msgid "Existing file attachment" #~ msgstr "Fichiers liés existant" -msgid "Current criteria" -msgstr "Critères choisis" +#~ msgid "Current criteria" +#~ msgstr "Critères choisis" -msgid "No filter" -msgstr "Aucun filtre" +#~ msgid "No filter" +#~ msgstr "Aucun filtre" diff --git a/mapentity/models.py b/mapentity/models.py index eeb2c7b64..4bc9b871a 100755 --- a/mapentity/models.py +++ b/mapentity/models.py @@ -5,12 +5,11 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError from django.contrib import auth -from django.contrib.admin.models import LogEntry, ADDITION +from django.contrib.admin.models import LogEntry as BaseLogEntry +from django.contrib.admin.models import ADDITION, CHANGE, DELETION from django.contrib.contenttypes.models import ContentType - - - - +from django.utils.formats import localize +from django.utils.translation import ugettext_lazy as _ from paperclip.models import Attachment from . import app_settings @@ -239,3 +238,29 @@ def authors(self): @property def last_author(self): return self.authors.order_by('logentry__pk').last() + + +class LogEntry(MapEntityMixin, BaseLogEntry): + geom = None + object_verbose_name = _("object") + + class Meta: + proxy = True + + @property + def action_flag_display(self): + return { + ADDITION: _("Added"), + CHANGE: _("Changed"), + DELETION: _("Deleted"), + }[self.action_flag] + + @property + def action_time_display(self): + return localize(self.action_time) + + @property + def object_display(self): + obj = self.get_edited_object() + return u'%s %s' % ( + obj.pk, obj.get_detail_url(), obj._meta.verbose_name.title(), unicode(obj)) diff --git a/mapentity/templates/mapentity/base.html b/mapentity/templates/mapentity/base.html index 519beee36..b4d6f1b04 100644 --- a/mapentity/templates/mapentity/base.html +++ b/mapentity/templates/mapentity/base.html @@ -119,7 +119,7 @@
  • {% trans "Admin" %}
  • {% endif %} - +
  • {% trans "Logout" %}
  • diff --git a/mapentity/templates/mapentity/logentry_list.html b/mapentity/templates/mapentity/logentry_list.html new file mode 100644 index 000000000..548da8a31 --- /dev/null +++ b/mapentity/templates/mapentity/logentry_list.html @@ -0,0 +1 @@ +{% extends "mapentity/entity_list.html" %} diff --git a/mapentity/urls.py b/mapentity/urls.py index 1688e8213..1b7540294 100644 --- a/mapentity/urls.py +++ b/mapentity/urls.py @@ -2,8 +2,10 @@ from django.conf.urls import patterns, url from . import app_settings +from . import registry from .views import (map_screenshot, convert, history_delete, serve_secure_media, JSSettings) +from .models import LogEntry _MEDIA_URL = settings.MEDIA_URL.replace(app_settings['ROOT_URL'], '') @@ -23,3 +25,5 @@ # Will be overriden, most probably. url(r'^api/settings.json$', JSSettings.as_view(), name='js_settings'), ) + +urlpatterns += registry.register(LogEntry, menu=False) diff --git a/mapentity/views/__init__.py b/mapentity/views/__init__.py index d1ccbc4ef..eb31ca0ea 100644 --- a/mapentity/views/__init__.py +++ b/mapentity/views/__init__.py @@ -21,6 +21,11 @@ map_screenshot, convert, history_delete) +from .logentry import (LogEntryList, + LogEntryJsonList, + LogEntryDetail, + LogEntryFormat, + LogEntryLayer) __all__ = ['MapEntityLayer', @@ -47,4 +52,10 @@ 'JSSettings', 'map_screenshot', 'convert', - 'history_delete'] + 'history_delete', + + 'LogEntryList', + 'LogEntryJsonList', + 'LogEntryDetail', + 'LogEntryFormat', + 'LogEntryLayer'] diff --git a/mapentity/views/logentry.py b/mapentity/views/logentry.py new file mode 100644 index 000000000..52437b8e0 --- /dev/null +++ b/mapentity/views/logentry.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from .generic import (MapEntityList, MapEntityJsonList, MapEntityDetail, + MapEntityFormat, MapEntityLayer) +from ..filters import BaseMapEntityFilterSet +from ..models import LogEntry +from .. import registry + + +class LogEntryFilter(BaseMapEntityFilterSet): + class Meta: + model = LogEntry + fields = ('user', ) + + +class LogEntryList(MapEntityList): + model = LogEntry + filterform = LogEntryFilter + columns = ('id', 'action_time', 'user', 'object', 'action_flag') + + def get_queryset(self): + queryset = super(LogEntryList, self).get_queryset() + return queryset.filter(content_type_id__in=registry.content_type_ids) + + def get_context_data(self, **kwargs): + context = super(LogEntryList, self).get_context_data(**kwargs) + context['can_add'] = False # There is no LogEntryCreate view + return context + + +class LogEntryJsonList(MapEntityJsonList, LogEntryList): + pass + + +class LogEntryDetail(MapEntityDetail): + model = LogEntry + + +class LogEntryFormat(MapEntityFormat): + model = LogEntry + filterform = LogEntryFilter + + +class LogEntryLayer(MapEntityLayer): + model = LogEntry From 88e0f090e56490a4b6cf297aeca8398ef756697b Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Thu, 17 Apr 2014 17:28:02 +0200 Subject: [PATCH 03/10] Add a tab with actions history in detail view --- mapentity/__init__.py | 1 + .../static/mapentity/mapentity.filter.js | 5 ++- mapentity/static/mapentity/style.css | 4 +++ .../templates/mapentity/entity_detail.html | 32 +++++++++++++++++++ mapentity/views/generic.py | 9 +++++- mapentity/views/logentry.py | 6 +++- 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/mapentity/__init__.py b/mapentity/__init__.py index 207ee95bf..f926fc81a 100755 --- a/mapentity/__init__.py +++ b/mapentity/__init__.py @@ -31,6 +31,7 @@ 'MAP_CAPTURE_MAX_RATIO': 1.25, 'GEOM_FIELD_NAME': 'geom', 'MAP_BACKGROUND_FOGGED': False, + 'ACTION_HISTORY_LENGTH': 20, 'ANONYMOUS_VIEWS_PERMS': tuple(), 'GEOJSON_LAYERS_CACHE_BACKEND': 'default' }, **getattr(settings, 'MAPENTITY_CONFIG', {})) diff --git a/mapentity/static/mapentity/mapentity.filter.js b/mapentity/static/mapentity/mapentity.filter.js index fa6733e8a..5bfe18010 100644 --- a/mapentity/static/mapentity/mapentity.filter.js +++ b/mapentity/static/mapentity/mapentity.filter.js @@ -128,7 +128,10 @@ MapEntity.TogglableFilter = L.Class.extend({ set = val !== '' && val != ['']; // Consider a value set if it is not the first option selected - if ($(field).is('select[multiple]')) { + if ($(field).is('input[type=hidden]')) { + set = false; + } + else if ($(field).is('select[multiple]')) { set = val !== null; } else if ($(field).is('select')) { diff --git a/mapentity/static/mapentity/style.css b/mapentity/static/mapentity/style.css index efaf65aa2..0334ea3ce 100644 --- a/mapentity/static/mapentity/style.css +++ b/mapentity/static/mapentity/style.css @@ -562,6 +562,10 @@ span.filter-info { margin-top: 2em; } +#history .btn { + float: right; + margin-top: 10px; +} /* Map capture */ diff --git a/mapentity/templates/mapentity/entity_detail.html b/mapentity/templates/mapentity/entity_detail.html index fb1b6331a..f858d1e30 100644 --- a/mapentity/templates/mapentity/entity_detail.html +++ b/mapentity/templates/mapentity/entity_detail.html @@ -26,6 +26,9 @@
  • {% trans "Attached files" %}
  • +
  • + {% trans "History" %} +
  • {% if can_edit %} {% trans "Update" %} {% else %} @@ -65,6 +68,35 @@

    {% trans "New file attachment" %}

    {% endif %} + +
    + + + + + + + + + + {% for logentry in logentries %} + + + + + + {% endfor %} + {% if logentries_hellip %} + + + + + + {% endif %} + +
    {% trans "Date" %}{% trans "User" %}{% trans "Action" %}
    {{ logentry.action_time }}{{ logentry.user }}{{ logentry.action_flag_display }}
    + {% trans "Historique complet" %} +
    diff --git a/mapentity/views/generic.py b/mapentity/views/generic.py index 31c6e4308..bc660c776 100755 --- a/mapentity/views/generic.py +++ b/mapentity/views/generic.py @@ -14,7 +14,6 @@ from django.template.base import TemplateDoesNotExist from django.template.defaultfilters import slugify from django.contrib import messages -from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from djgeojson.views import GeoJSONLayerView from djappypod.odt import get_template from djappypod.response import OdtTemplateResponse @@ -24,6 +23,7 @@ from .. import models as mapentity_models from ..helpers import convertit_url, download_to_stream, user_has_perm from ..decorators import save_history, view_permission_required, view_cache_latest +from ..models import LogEntry, ADDITION, CHANGE, DELETION from ..serializers import GPXSerializer, CSVSerializer, DatatablesSerializer, ZipShapeSerializer from ..filters import MapEntityFilterSet from .base import history_delete @@ -391,8 +391,15 @@ def dispatch(self, *args, **kwargs): def get_context_data(self, **kwargs): context = super(MapEntityDetail, self).get_context_data(**kwargs) + logentries_max = app_settings['ACTION_HISTORY_LENGTH'] + logentries = LogEntry.objects.filter( + content_type_id=self.object.get_content_type_id(), + object_id=self.object.pk + ).order_by('-id') context['activetab'] = self.request.GET.get('tab') context['empty_map_message'] = _("No map available for this object.") + context['logentries'] = logentries[:logentries_max] + context['logentries_hellip'] = logentries.count() > logentries_max perm_update = self.model.get_permission_codename(mapentity_models.ENTITY_UPDATE) can_edit = user_has_perm(self.request.user, perm_update) diff --git a/mapentity/views/logentry.py b/mapentity/views/logentry.py index 52437b8e0..4fe4e96d1 100644 --- a/mapentity/views/logentry.py +++ b/mapentity/views/logentry.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import django_filters +from django import forms from .generic import (MapEntityList, MapEntityJsonList, MapEntityDetail, MapEntityFormat, MapEntityLayer) from ..filters import BaseMapEntityFilterSet @@ -7,9 +9,11 @@ class LogEntryFilter(BaseMapEntityFilterSet): + content_type = django_filters.NumberFilter(widget=forms.HiddenInput) + object_id = django_filters.NumberFilter(widget=forms.HiddenInput) class Meta: model = LogEntry - fields = ('user', ) + fields = ('user', 'content_type', 'object_id') class LogEntryList(MapEntityList): From 907a9bd1995bdc5b26330bc1511dbaeed7bc9c94 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Thu, 17 Apr 2014 17:45:47 +0200 Subject: [PATCH 04/10] Make actions history optional --- mapentity/__init__.py | 1 + mapentity/context_processors.py | 1 + mapentity/templates/mapentity/base.html | 6 +- .../templates/mapentity/entity_detail.html | 62 ++++++++++--------- mapentity/urls.py | 6 +- mapentity/views/generic.py | 2 + 6 files changed, 45 insertions(+), 33 deletions(-) diff --git a/mapentity/__init__.py b/mapentity/__init__.py index f926fc81a..2736ce6f3 100755 --- a/mapentity/__init__.py +++ b/mapentity/__init__.py @@ -31,6 +31,7 @@ 'MAP_CAPTURE_MAX_RATIO': 1.25, 'GEOM_FIELD_NAME': 'geom', 'MAP_BACKGROUND_FOGGED': False, + 'ACTION_HISTORY_ENABLED': True, 'ACTION_HISTORY_LENGTH': 20, 'ANONYMOUS_VIEWS_PERMS': tuple(), 'GEOJSON_LAYERS_CACHE_BACKEND': 'default' diff --git a/mapentity/context_processors.py b/mapentity/context_processors.py index 75a196733..154c62b2b 100644 --- a/mapentity/context_processors.py +++ b/mapentity/context_processors.py @@ -11,5 +11,6 @@ def settings(request): JS_SETTINGS_VIEW=app_settings['JS_SETTINGS_VIEW'], TRANSLATED_LANGUAGES=app_settings['TRANSLATED_LANGUAGES'], MAP_BACKGROUND_FOGGED=app_settings['MAP_BACKGROUND_FOGGED'], + ACTION_HISTORY_ENABLED=app_settings['ACTION_HISTORY_ENABLED'], registry=registry, ) diff --git a/mapentity/templates/mapentity/base.html b/mapentity/templates/mapentity/base.html index b4d6f1b04..be5e0b625 100644 --- a/mapentity/templates/mapentity/base.html +++ b/mapentity/templates/mapentity/base.html @@ -119,8 +119,10 @@
  • {% trans "Admin" %}
  • {% endif %} - -
  • + {% if ACTION_HISTORY_ENABLED %} + +
  • + {% endif %}
  • {% trans "Logout" %}
  • diff --git a/mapentity/templates/mapentity/entity_detail.html b/mapentity/templates/mapentity/entity_detail.html index f858d1e30..83fc2fc99 100644 --- a/mapentity/templates/mapentity/entity_detail.html +++ b/mapentity/templates/mapentity/entity_detail.html @@ -26,9 +26,11 @@
  • {% trans "Attached files" %}
  • -
  • - {% trans "History" %} -
  • + {% if ACTION_HISTORY_ENABLED %} +
  • + {% trans "History" %} +
  • + {% endif %} {% if can_edit %} {% trans "Update" %} {% else %} @@ -69,34 +71,36 @@

    {% trans "New file attachment" %}

    {% endif %} -
    - - - - - - - - - - {% for logentry in logentries %} - - - - - - {% endfor %} - {% if logentries_hellip %} + {% if ACTION_HISTORY_ENABLED %} +
    +
    {% trans "Date" %}{% trans "User" %}{% trans "Action" %}
    {{ logentry.action_time }}{{ logentry.user }}{{ logentry.action_flag_display }}
    + - - - + + + - {% endif %} - -
    {% trans "Date" %}{% trans "User" %}{% trans "Action" %}
    - {% trans "Historique complet" %} -
    + + + {% for logentry in logentries %} + + {{ logentry.action_time }} + {{ logentry.user }} + {{ logentry.action_flag_display }} + + {% endfor %} + {% if logentries_hellip %} + + … + … + … + + {% endif %} + + + {% trans "Historique complet" %} + + {% endif %} diff --git a/mapentity/urls.py b/mapentity/urls.py index 1b7540294..ba3ce470d 100644 --- a/mapentity/urls.py +++ b/mapentity/urls.py @@ -5,7 +5,8 @@ from . import registry from .views import (map_screenshot, convert, history_delete, serve_secure_media, JSSettings) -from .models import LogEntry +if app_settings['ACTION_HISTORY_ENABLED']: + from .models import LogEntry _MEDIA_URL = settings.MEDIA_URL.replace(app_settings['ROOT_URL'], '') @@ -26,4 +27,5 @@ url(r'^api/settings.json$', JSSettings.as_view(), name='js_settings'), ) -urlpatterns += registry.register(LogEntry, menu=False) +if app_settings['ACTION_HISTORY_ENABLED']: + urlpatterns += registry.register(LogEntry, menu=False) diff --git a/mapentity/views/generic.py b/mapentity/views/generic.py index bc660c776..e1335f6b3 100755 --- a/mapentity/views/generic.py +++ b/mapentity/views/generic.py @@ -34,6 +34,8 @@ def log_action(request, object, action_flag): + if not app_settings['ACTION_HISTORY_ENABLED']: + return LogEntry.objects.log_action( user_id=request.user.pk, content_type_id=object.get_content_type_id(), From 2b01a0605c3119b5112b0f9753d811e838487e61 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Thu, 17 Apr 2014 18:24:53 +0200 Subject: [PATCH 05/10] Fix syncdb --- mapentity/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mapentity/models.py b/mapentity/models.py index 4bc9b871a..ed2aa6c21 100755 --- a/mapentity/models.py +++ b/mapentity/models.py @@ -1,6 +1,7 @@ import os from django.db import models +from django.db.utils import OperationalError from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError @@ -219,7 +220,10 @@ def get_attributes_html(self, rooturl): @classmethod def get_content_type_id(cls): - return ContentType.objects.get_for_model(cls).pk + try: + return ContentType.objects.get_for_model(cls).pk + except OperationalError: # table is not yet created + return None @property def creator(self): From f9a6ce0b1a8930d6157e936bbbac817a1ccc6e49 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Thu, 17 Apr 2014 18:25:05 +0200 Subject: [PATCH 06/10] Add creator, authors and last author in trackinfo fragment --- mapentity/templates/mapentity/trackinfo_fragment.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mapentity/templates/mapentity/trackinfo_fragment.html b/mapentity/templates/mapentity/trackinfo_fragment.html index d9b0ef330..dfa370ab1 100644 --- a/mapentity/templates/mapentity/trackinfo_fragment.html +++ b/mapentity/templates/mapentity/trackinfo_fragment.html @@ -5,4 +5,13 @@ {{ object|verbose:"date_update" }} {{ object.date_update }} ({{ object.date_update|timesince }}) - \ No newline at end of file + + Creator + {{ object.creator }} + + Authors + {{ object.authors|join:", " }} + + Last author + {{ object.last_author|default:"" }} + From 78db05262bf76f5b3dc893f123d7cfc8b99b3018 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Wed, 23 Apr 2014 13:57:06 +0200 Subject: [PATCH 07/10] Show time since for action history --- mapentity/models.py | 4 +++- mapentity/templates/mapentity/entity_detail.html | 4 ++-- .../templates/mapentity/trackinfo_fragment.html | 12 ++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mapentity/models.py b/mapentity/models.py index ed2aa6c21..66021bb27 100755 --- a/mapentity/models.py +++ b/mapentity/models.py @@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _ from paperclip.models import Attachment +from mapentity.templatetags.timesince import humanize_timesince from . import app_settings from .helpers import smart_urljoin, is_file_newer, capture_map_image, extract_attributes_html @@ -261,7 +262,8 @@ def action_flag_display(self): @property def action_time_display(self): - return localize(self.action_time) + return u'{0} ({1})'.format(localize(self.action_time), + humanize_timesince(self.action_time)) @property def object_display(self): diff --git a/mapentity/templates/mapentity/entity_detail.html b/mapentity/templates/mapentity/entity_detail.html index 83fc2fc99..f8676ae13 100644 --- a/mapentity/templates/mapentity/entity_detail.html +++ b/mapentity/templates/mapentity/entity_detail.html @@ -1,5 +1,5 @@ {% extends "mapentity/base.html" %} -{% load i18n static field_verbose_name mapentity leaflet_tags geojson_tags attachments_tags %} +{% load i18n static field_verbose_name mapentity leaflet_tags geojson_tags attachments_tags timesince %} {% block title %}{{ object }} | {{ block.super }}{% endblock title %} @@ -84,7 +84,7 @@

    {% trans "New file attachment" %}

    {% for logentry in logentries %} - {{ logentry.action_time }} + {{ logentry.action_time }} ({{ logentry.action_time|timesince }}) {{ logentry.user }} {{ logentry.action_flag_display }} diff --git a/mapentity/templates/mapentity/trackinfo_fragment.html b/mapentity/templates/mapentity/trackinfo_fragment.html index dfa370ab1..6828cfc43 100644 --- a/mapentity/templates/mapentity/trackinfo_fragment.html +++ b/mapentity/templates/mapentity/trackinfo_fragment.html @@ -1,4 +1,4 @@ -{% load field_verbose_name timesince %} +{% load field_verbose_name timesince i18n %} {{ object|verbose:"date_insert" }} {{ object.date_insert }} ({{ object.date_insert|timesince }}) @@ -6,12 +6,12 @@ {{ object|verbose:"date_update" }} {{ object.date_update }} ({{ object.date_update|timesince }}) - Creator + {% trans "Last author" %} + {{ object.last_author|default:"" }} + + {% trans "Creator" %} {{ object.creator }} - Authors + {% trans "Authors" %} {{ object.authors|join:", " }} - - Last author - {{ object.last_author|default:"" }} From d0b7d7d6a9abeef98e5daca57d8a8cea9c3a5a39 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Wed, 23 Apr 2014 15:33:03 +0200 Subject: [PATCH 08/10] Test actions history --- mapentity/tests/__init__.py | 1 + mapentity/tests/test_history.py | 50 +++++++++++++++++++++++++++++++++ mapentity/tests/test_views.py | 4 +-- mapentity/urlizor.py | 2 +- mapentity/views/base.py | 3 +- 5 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 mapentity/tests/test_history.py diff --git a/mapentity/tests/__init__.py b/mapentity/tests/__init__.py index 4cc7b2a36..21df908d9 100644 --- a/mapentity/tests/__init__.py +++ b/mapentity/tests/__init__.py @@ -8,3 +8,4 @@ from .test_serializers import * # noqa from .test_decorators import * # noqa from .test_permissions import * # noqa +from .test_history import * # noqa diff --git a/mapentity/tests/test_history.py b/mapentity/tests/test_history.py new file mode 100644 index 000000000..3c46032af --- /dev/null +++ b/mapentity/tests/test_history.py @@ -0,0 +1,50 @@ +from django.contrib.admin.models import ADDITION, CHANGE, DELETION +from django.contrib.auth import get_user_model +from django.test.client import Client +from django.test import TestCase + +from ..models import LogEntry +from .models import DummyModel + + +User = get_user_model() + + +class TestActionsHistory(TestCase): + def setUp(self): + self.client = Client() + self.user = User.objects.create_superuser('test', 'email@corp.com', 'booh') + self.client.login(username='test', password='booh') + + def test_create_view_logs_addition(self): + response = self.client.post('/dummymodel/add/', data={ + 'geom': '{"type": "Point", "coordinates": [0, 0]}', + 'model': 'dummymodel', + }) + self.assertEqual(LogEntry.objects.count(), 1) + entry = LogEntry.objects.get() + obj = DummyModel.objects.get() + self.assertEqual(entry.get_edited_object(), obj) + self.assertEqual(entry.action_flag, ADDITION) + self.assertEqual(entry.user, self.user) + + def test_update_view_logs_change(self): + obj = DummyModel.objects.create() + response = self.client.post('/dummymodel/edit/{0}/'.format(obj.pk), data={ + 'geom': '{"type": "Point", "coordinates": [0, 0]}', + 'model': 'dummymodel', + }) + self.assertEqual(LogEntry.objects.count(), 1) + entry = LogEntry.objects.get() + self.assertEqual(entry.get_edited_object(), obj) + self.assertEqual(entry.action_flag, CHANGE) + self.assertEqual(entry.user, self.user) + + def test_delete_view_logs_deletion(self): + obj = DummyModel.objects.create() + response = self.client.post('/dummymodel/delete/{0}/'.format(obj.pk)) + self.assertEqual(LogEntry.objects.count(), 1) + entry = LogEntry.objects.get() + self.assertEqual(entry.object_id, str(obj.pk)) + self.assertEqual(entry.action_flag, DELETION) + self.assertEqual(entry.user, self.user) diff --git a/mapentity/tests/test_views.py b/mapentity/tests/test_views.py index ef59cc9bc..827574216 100644 --- a/mapentity/tests/test_views.py +++ b/mapentity/tests/test_views.py @@ -184,7 +184,7 @@ def test_unauthorized_update_view_redirects_to_detail(self): target_status_code=302) # --> login def test_unauthorized_delete_view_redirects_to_detail(self): - delete_url = '/dummymodel/delete/%s' % self.object.pk - response = self.client.get('/dummymodel/delete/%s' % self.object.pk) + delete_url = '/dummymodel/delete/%s/' % self.object.pk + response = self.client.get(delete_url) self.assertRedirects(response, 'http://testserver/dummymodel/%s/?next=%s' % (self.object.pk, delete_url), target_status_code=302) # --> login diff --git a/mapentity/urlizor.py b/mapentity/urlizor.py index 61dcce6b0..330e619b1 100644 --- a/mapentity/urlizor.py +++ b/mapentity/urlizor.py @@ -49,7 +49,7 @@ def url_update(model): def url_delete(model): - return r'^{0}/delete/(?P\d+)$'.format(frommodel(model)) + return r'^{0}/delete/(?P\d+)/$'.format(frommodel(model)) kind_to_urlpath = { diff --git a/mapentity/views/base.py b/mapentity/views/base.py index c4a26a62c..64e789e2e 100644 --- a/mapentity/views/base.py +++ b/mapentity/views/base.py @@ -186,7 +186,8 @@ def convert(request): def history_delete(request, path=None): path = request.POST.get('path', path) if path: - history = request.session['history'] + history = request.session.get('history') + if history: history = [h for h in history if h['path'] != path] request.session['history'] = history return HttpResponse() From b90bfcb4fa41314062ef82843219cb4cc0ca30ec Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Wed, 23 Apr 2014 16:39:05 +0200 Subject: [PATCH 09/10] French translation --- mapentity/locale/fr/LC_MESSAGES/django.mo | Bin 2482 -> 3506 bytes mapentity/locale/fr/LC_MESSAGES/django.po | 184 +++++++++++------- .../templates/mapentity/entity_detail.html | 2 +- 3 files changed, 114 insertions(+), 72 deletions(-) diff --git a/mapentity/locale/fr/LC_MESSAGES/django.mo b/mapentity/locale/fr/LC_MESSAGES/django.mo index fdf19789b8a3150081a148dce07f15d55caedcbc..6d2e9f2c6639971baa8b4cd4b6fa86e344023241 100644 GIT binary patch literal 3506 zcmai#OKc=Z9mXqRvms6bVG{@&2owazyR3U`uMIe}_L9lWc;jToHXeJ$f`lk{muG6X zr+cXC9>;+TLO}{S01_OKkOCL7!y!?`1tF0`25~@0NIVV*#35XAKq80b0zu;Yy31oD z4zyhJt9Soj{rN9vPJLJ4Sw;IG+Ns-x=zxEF2Y&D@-yy{Nz{kM%gIgf?89)BI=ZoMv z=6?#F25-j?ueb}m2fP==l6c5dg73umQSe>h9C#;q8RWS&5P#w+KmQp&zUIeY^z4E6 zVc!7cxvzq!z~?~xiErSC_kJ6!g5UGw--GxQf5s2(`zy%vZ-Lx@3w#iK8@wBQ0F%5| zf?S`*50*p#a^Dj89Hd;L{-Us6p=k3WzJj*FbZd5HEm? z<3Die8SpfWcmO;L@>~U^Jx#C$ZiBq%MUeOW2&7*x`|&S8+WTve`+f^D&wupmZ-Pa< z{rDCL6U5sf_um00$a_Hgc|XWFJPh*O97ul`{ra-!6W|KwTOh6y&x5q@J0Sh~KFIU0 z`1PNF^z${4_Wcf|-EV+A_a;cc-vZBr{|4#j3?^ysD#-I|AoqP9KQmwtl;F2O?t2yF zdw=Hn8i+sfI(~4a_zlQ<`I8_29i;vL0_p$hd&_(71G)b!NPDNjPk~F|N5L3moW2Rt zj~{}(=Oqwn79T+4Gh0F|f_R1a7+M95I`9cJ$Uspy3*cjDOK7~0-#+ccbz&avELVGiGoM&BRxlgtxy!+Ky1&!J7D zF)n;Ap?w&Q-zj6k=OP;Gn++@9=D7WyX1GkvsFjwXu-THiQYVt=x5sMX{;_RED@7;5KamddQBrPW``waavb zp2O=yEsbM8vnIrJMP5sD*;7L;_q0y15*e$D6;TT_lP01TMQl>ZL?6g$$ARV)>=d=+ zQ1acPmSrmJ;XV5%*4UG0z0^8U?_3pim4rGL^`1()I9Rt@W!l3JQ!DDZwK~aUXicWA zQKGR~7Y*)g=vYq>iAE|9VKZ)t<3o91T#wF1#U{A|$I_&GdHP(scO8D5cPn##N`6{2 zBa_jCH520es} zv=F^M?o>l%Vzq}|`>91tqHEgC`d|x|j)GfBim1eK zdVuu945v?e<0{-|X?b6UX_6_EIH|2=d0fvKP`QsFIE}@qtl`o*filhG%+UenGFXp- ztJ*pgWmRs~>QC3MH09Oic4vKaV`^IuQkw;B*ELb_MBa75PFj^yTbnyUQE)gEu$rp! z;{3u=FuxRBT$Bq-)y2gN^LVf^*w#bC%l*D<&DwU*-V7Qw`Q&D`y?)_yn;Xq3mO!v$ zRpMfWx~a;w-BwF()Y?tC(rnk(TaVWojqPTqa~WHcZjP`9J6iRtvQ^u-vP+X!TCK-p zQ%P<2cn?f%ueY1W{w-AIr|O6n>LJ)U9Kh~OKa-2)P(Lfjqij6F64c3b$D_(boM0|Llv+uAaj3?8s8wDl6=%nziB=_e2R{C= zXHZ5mHsevDRB*6Vs#=odF>WirP8_UToPag~euNT2eCiOX@l9BYOr$6pcdS;Od@!)4 zk5j&fgYYBP&jv%fGqeh;5IT3X@&Aa{_<4NR+8*ha#m%Ko8Av<6sQv)*ctXB7Ev}45 zX&*te27+Iz8HLm)@SF-pC#YarJcWEGDi)3L2p?dgpP}Zrlth##HzYGk4ipksWWgx} zxT`EP25FVq|6jUikz-T<#m*be#2oi!JaZ&{6Q{(s z#(y$MAh%9L&?~kTd1hkc$~xH0&Fpa^nP(@HnemS^M1uiCq*H4gN*5*%Q&YxiV8%D` zPXM19s$4@LTZORqb9~&8Q;MWvg+$l`p)AT1Nsalh-0LK?oFbVCR3=4IK_>qLAF53- delta 1142 zcmX|=O-NKx6o8NE{OF8nW7>~@uisIT&;BUsQ-EfQPa zgjTkz26wt`5ezO`v=D+2LMU3)qD4V%g6canHxKvR@0|1QyXV|<-&<{6Z7OWVYL*1) zLEBKjO5`+rwSx(HSuN56i*PS|4Rzh7^?zA5)QDh87mUJQ7=VM8XJ8or9NY=VVNj$X zQy3uviAq3x>u0Th*ZL3O5bGALziywug$#LTL^W1*jWcfqJs*@F2`Uy^1BM>t8@RT5h){RMB-u%T^_sW9|J=-_>tG z+pls&@1H%kWe=8a+==#VM=RH&t`?)B*P+sYcB6+;{X71Pp02|>`nR;BdLZqpc22KN zI}+<+sz>#2)7Go6atLiLTa~XnZu$cYrY?BGj0HQ)VsO$pp=L83>NE?X`R>c9bjnZN zoOiselT78Dxf$O}9FvK3VkYOxq?_$^60Yy&Tr(CvZXSlOhWiJczQLjXQ)^$tUX^)X zH`<;|=6yFK7gGuEhF2=eXr@%eSCoq8WW+U}BCTd5+F@p+%RM%u=Q>`poXxdw^2(*W b*Xv~S>2$`s?Pkoa`hf_WWb=M*CYP0e`nZJo diff --git a/mapentity/locale/fr/LC_MESSAGES/django.po b/mapentity/locale/fr/LC_MESSAGES/django.po index 85018c396..6e3a55d04 100644 --- a/mapentity/locale/fr/LC_MESSAGES/django.po +++ b/mapentity/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-16 09:18+0000\n" +"POT-Creation-Date: 2014-04-23 14:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,12 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" +#: decorators.py:31 +msgid "" +"Access to the requested resource is restricted. You have been redirected." +msgstr "" +"L'accès à la ressource demandée est restreint. Vous avez été redirigé." + #: filters.py:21 msgid "Any year" msgstr "Année" @@ -37,15 +43,19 @@ msgstr "Annuler" msgid "Delete" msgstr "Supprimer" -#: models.py:200 +#: models.py:250 +msgid "object" +msgstr "objet" + +#: models.py:258 msgid "Added" msgstr "Ajouté" -#: models.py:201 +#: models.py:259 msgid "Changed" msgstr "Modifié" -#: models.py:202 +#: models.py:260 msgid "Deleted" msgstr "Supprimé" @@ -58,22 +68,50 @@ msgstr "Aucun(e)" msgid "Modified" msgstr "Modifié" -#: templates/mapentity/base.html:66 +#: templates/mapentity/base.html:68 msgid "List" msgstr "Liste" -#: templates/mapentity/base.html:117 +#: templates/mapentity/base.html:119 msgid "Admin" msgstr "Admin" -#: templates/mapentity/base.html:120 +#: templates/mapentity/base.html:123 msgid "Logbook" msgstr "Journal" -#: templates/mapentity/base.html:123 +#: templates/mapentity/base.html:127 msgid "Logout" msgstr "Déconnexion" +#: templates/mapentity/base.html:189 +msgid "Measure distances" +msgstr "Mesurer les distances" + +#: templates/mapentity/base.html:190 +msgid "Save map as image" +msgstr "Sauvegarder l'image" + +#: templates/mapentity/base.html:192 +msgid "Map geometry is unsaved" +msgstr "La géométrie n'a pas été sauvegardée" + +#: templates/mapentity/base.html:195 +msgid "Search" +msgstr "Rechercher" + +#: templates/mapentity/base.html:196 +msgid "results" +msgstr "résultats" + +#: templates/mapentity/base.html:197 +msgid "Current criteria" +msgstr "Critères choisis" + +#: templates/mapentity/base.html:198 +msgid "No filter" +msgstr "Aucun filtre" + #: templates/mapentity/entity_confirm_delete.html:17 #, python-format msgid "Do you really wish to delete %(object)s ?" @@ -87,59 +125,95 @@ msgstr "Oui, supprimer" msgid "No, back to edit view" msgstr "Non, retour à la vue d'édition" -#: templates/mapentity/entity_detail.html:11 +#: templates/mapentity/entity_detail.html:12 msgid "ODT" msgstr "ODT" #: templates/mapentity/entity_detail.html:13 +msgid "DOC" +msgstr "DOC" + +#: templates/mapentity/entity_detail.html:14 msgid "PDF" msgstr "PDF" -#: templates/mapentity/entity_detail.html:21 +#: templates/mapentity/entity_detail.html:22 msgid "Properties" msgstr "Propriétés" -#: templates/mapentity/entity_detail.html:26 -#: templates/mapentity/entity_detail.html:49 +#: templates/mapentity/entity_detail.html:27 +#: templates/mapentity/entity_detail.html:55 msgid "Attached files" msgstr "Fichiers liés" -#: templates/mapentity/entity_detail.html:29 #: templates/mapentity/entity_detail.html:31 +msgid "History" +msgstr "Historique" + +#: templates/mapentity/entity_detail.html:35 +#: templates/mapentity/entity_detail.html:37 msgid "Update" msgstr "Modifier" -#: templates/mapentity/entity_detail.html:57 +#: templates/mapentity/entity_detail.html:62 +msgid "You are not allowed to see attachments." +msgstr "Vous n'êtes pas autorisé à voir les pièces jointes." + +#: templates/mapentity/entity_detail.html:68 msgid "New file attachment" msgstr "Nouveau fichier lié" -#: templates/mapentity/entity_detail.html:71 -msgid "No map available for this object." -msgstr "Pas de carte disponible pour cet objet." +#: templates/mapentity/entity_detail.html:79 +msgid "Date" +msgstr "Date" + +#: templates/mapentity/entity_detail.html:80 +msgid "User" +msgstr "utilisateur" + +#: templates/mapentity/entity_detail.html:81 +msgid "Action" +msgstr "Action" + +#: templates/mapentity/entity_detail.html:101 +msgid "Full history" +msgstr "Historique complet" -#: templates/mapentity/entity_list.html:30 #: templates/mapentity/entity_list.html:32 +#: templates/mapentity/entity_list.html:34 #: templates/mapentity/popupplus.html:8 msgid "Add" msgstr "Ajouter" -#: templates/mapentity/entity_list.html:115 -msgid "Save map as image" -msgstr "Sauvegarder l'image" +#: templates/mapentity/entity_list.html:58 +msgid "CSV" +msgstr "CSV" -#: templates/mapentity/entity_list.html:180 -msgid "Search" -msgstr "Rechercher" +#: templates/mapentity/entity_list.html:59 +msgid "Shapefile" +msgstr "Shapefile" -#: templates/mapentity/entity_list.html:202 -msgid "results" -msgstr "résultats" +#: templates/mapentity/entity_list.html:60 +msgid "GPX" +msgstr "GPX" #: templates/mapentity/entity_list_filter_fragment.html:3 #: templates/mapentity/entity_list_filter_fragment.html:17 msgid "Filter" msgstr "Filtrer" +#: templates/mapentity/trackinfo_fragment.html:9 +msgid "Last author" +msgstr "Dernier auteur" + +#: templates/mapentity/trackinfo_fragment.html:12 +msgid "Creator" +msgstr "Créateur" + +#: templates/mapentity/trackinfo_fragment.html:15 +msgid "Authors" +msgstr "Auteurs" + #: templatetags/timesince.py:21 #, python-format msgid "%d year ago" @@ -179,64 +253,32 @@ msgstr[1] "%d minutes" msgid "just a few seconds ago" msgstr "quelques secondes" -#: tests/test_functional.py:48 -msgid "Topology is not valid." -msgstr "Topologie invalide" +#: tests/test_functional.py:49 +msgid "Invalid geometry value." +msgstr "Géométrie invalide" -#: views/generic.py:334 +#: views/generic.py:352 #, python-format msgid "Add a new %s" msgstr "Ajouter un nouveau %s" -#: views/generic.py:347 +#: views/generic.py:365 msgid "Created" msgstr "Créé" -#: views/generic.py:352 views/generic.py:413 +#: views/generic.py:370 views/generic.py:447 msgid "Your form contains errors" msgstr "Le formulaire contient des erreurs" -#: views/generic.py:391 +#: views/generic.py:402 +msgid "No map available for this object." +msgstr "Pas de carte disponible pour cet objet." + +#: views/generic.py:425 #, python-format msgid "Edit %s" msgstr "Éditer %s" -#: views/generic.py:408 +#: views/generic.py:442 msgid "Saved" msgstr "Sauvegardé" - -#~ msgid "Path" -#~ msgstr "Tronçon" - -#~ msgid "Intervention" -#~ msgstr "Intervention" - -#~ msgid "Project" -#~ msgstr "Chantier" - -#~ msgid "Signage" -#~ msgstr "Signalétique" - -#~ msgid "Infrastructure" -#~ msgstr "Aménagement" - -#~ msgid "Trek" -#~ msgstr "Itinéraire" - -#~ msgid "POI" -#~ msgstr "POI" - -#~ msgid "Structure" -#~ msgstr "Aménagement" - -#~ msgid "Unknown serialization format '%s'" -#~ msgstr "Format de sérialization inconnnu '%s'" - -#~ msgid "Existing file attachment" -#~ msgstr "Fichiers liés existant" - -#~ msgid "Current criteria" -#~ msgstr "Critères choisis" - -#~ msgid "No filter" -#~ msgstr "Aucun filtre" diff --git a/mapentity/templates/mapentity/entity_detail.html b/mapentity/templates/mapentity/entity_detail.html index f8676ae13..ca07165af 100644 --- a/mapentity/templates/mapentity/entity_detail.html +++ b/mapentity/templates/mapentity/entity_detail.html @@ -98,7 +98,7 @@

    {% trans "New file attachment" %}

    {% endif %} - {% trans "Historique complet" %} + {% trans "Full history" %} {% endif %} From 32316bf5d66434ee3ea69657bc0f03a3a131b2c5 Mon Sep 17 00:00:00 2001 From: Gael UTARD Date: Wed, 23 Apr 2014 17:04:32 +0200 Subject: [PATCH 10/10] PEP8/pyflakes --- mapentity/models.py | 1 - mapentity/tests/test_history.py | 6 +++--- mapentity/views/logentry.py | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mapentity/models.py b/mapentity/models.py index 2585b03e3..8bb3e9f07 100755 --- a/mapentity/models.py +++ b/mapentity/models.py @@ -8,7 +8,6 @@ from django.contrib import auth from django.contrib.admin.models import LogEntry as BaseLogEntry from django.contrib.admin.models import ADDITION, CHANGE, DELETION -from django.contrib.contenttypes.models import ContentType from django.utils.formats import localize from django.utils.translation import ugettext_lazy as _ from paperclip.models import Attachment diff --git a/mapentity/tests/test_history.py b/mapentity/tests/test_history.py index 3c46032af..9ee2d9017 100644 --- a/mapentity/tests/test_history.py +++ b/mapentity/tests/test_history.py @@ -17,7 +17,7 @@ def setUp(self): self.client.login(username='test', password='booh') def test_create_view_logs_addition(self): - response = self.client.post('/dummymodel/add/', data={ + self.client.post('/dummymodel/add/', data={ 'geom': '{"type": "Point", "coordinates": [0, 0]}', 'model': 'dummymodel', }) @@ -30,7 +30,7 @@ def test_create_view_logs_addition(self): def test_update_view_logs_change(self): obj = DummyModel.objects.create() - response = self.client.post('/dummymodel/edit/{0}/'.format(obj.pk), data={ + self.client.post('/dummymodel/edit/{0}/'.format(obj.pk), data={ 'geom': '{"type": "Point", "coordinates": [0, 0]}', 'model': 'dummymodel', }) @@ -42,7 +42,7 @@ def test_update_view_logs_change(self): def test_delete_view_logs_deletion(self): obj = DummyModel.objects.create() - response = self.client.post('/dummymodel/delete/{0}/'.format(obj.pk)) + self.client.post('/dummymodel/delete/{0}/'.format(obj.pk)) self.assertEqual(LogEntry.objects.count(), 1) entry = LogEntry.objects.get() self.assertEqual(entry.object_id, str(obj.pk)) diff --git a/mapentity/views/logentry.py b/mapentity/views/logentry.py index ecda59ce9..6d6d83f2b 100644 --- a/mapentity/views/logentry.py +++ b/mapentity/views/logentry.py @@ -11,6 +11,7 @@ class LogEntryFilter(BaseMapEntityFilterSet): content_type = django_filters.NumberFilter(widget=forms.HiddenInput) object_id = django_filters.NumberFilter(widget=forms.HiddenInput) + class Meta: model = LogEntry fields = ('user', 'content_type', 'object_id')