From aa96ebc333a86e9600aa82aaf6a09adac21abe91 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Thu, 19 Dec 2013 22:55:14 -0500 Subject: [PATCH] Updates to webapp: move cache updates to save of models to make it so that they cannot become out of sync with data; moved svg images to png forms to work with all browsers --- gramps/webapp/dbdjango.py | 37 ++-- gramps/webapp/grampsdb/models.py | 87 ++++++++ gramps/webapp/grampsdb/view/citation.py | 11 +- gramps/webapp/grampsdb/view/event.py | 12 +- gramps/webapp/grampsdb/view/family.py | 6 +- gramps/webapp/grampsdb/view/media.py | 10 +- gramps/webapp/grampsdb/view/note.py | 8 +- gramps/webapp/grampsdb/view/person.py | 18 +- gramps/webapp/grampsdb/view/place.py | 4 +- gramps/webapp/grampsdb/view/repository.py | 6 +- gramps/webapp/grampsdb/view/source.py | 2 +- gramps/webapp/grampsdb/view/tag.py | 2 +- gramps/webapp/grampsdb/views.py | 52 +++-- gramps/webapp/init.py | 2 +- gramps/webapp/libdjango.py | 246 +++++++--------------- gramps/webapp/reports.py | 4 - gramps/webapp/utils.py | 8 +- images/add-parent-existing-family.png | Bin 0 -> 1593 bytes images/gramps-parents-add.png | Bin 0 -> 1637 bytes images/gramps-parents-open.png | Bin 0 -> 1700 bytes images/gramps-parents.png | Bin 0 -> 1552 bytes 21 files changed, 249 insertions(+), 266 deletions(-) create mode 100644 images/add-parent-existing-family.png create mode 100644 images/gramps-parents-add.png create mode 100644 images/gramps-parents-open.png create mode 100644 images/gramps-parents.png diff --git a/gramps/webapp/dbdjango.py b/gramps/webapp/dbdjango.py index bc50650a3..d0b1c6ad5 100644 --- a/gramps/webapp/dbdjango.py +++ b/gramps/webapp/dbdjango.py @@ -27,11 +27,6 @@ # #------------------------------------------------------------------------ import sys -if sys.version_info[0] < 3: - import cPickle as pickle -else: - import pickle -import base64 import time import re import gramps @@ -723,49 +718,49 @@ class DbDjango(DbWriteBase, DbReadBase): def make_repository(self, repository): if self.use_db_cache and repository.cache: - data = pickle.loads(base64.decodestring(repository.cache)) + data = repository.from_cache() else: data = self.dji.get_repository(repository) return Repository.create(data) def make_citation(self, citation): if self.use_db_cache and citation.cache: - data = pickle.loads(base64.decodestring(citation.cache)) + data = citation.from_cache() else: data = self.dji.get_citation(citation) return Citation.create(data) def make_source(self, source): if self.use_db_cache and source.cache: - data = pickle.loads(base64.decodestring(source.cache)) + data = source.from_cache() else: data = self.dji.get_source(source) return Source.create(data) def make_family(self, family): if self.use_db_cache and family.cache: - data = pickle.loads(base64.decodestring(family.cache)) + data = family.from_cache() else: data = self.dji.get_family(family) return Family.create(data) def make_person(self, person): if self.use_db_cache and person.cache: - data = pickle.loads(base64.decodestring(person.cache)) + data = person.from_cache() else: data = self.dji.get_person(person) return Person.create(data) def make_event(self, event): if self.use_db_cache and event.cache: - data = pickle.loads(base64.decodestring(event.cache)) + data = event.from_cache() else: data = self.dji.get_event(event) return Event.create(data) def make_note(self, note): if self.use_db_cache and note.cache: - data = pickle.loads(base64.decodestring(note.cache)) + data = note.from_cache() else: data = self.dji.get_note(note) return Note.create(data) @@ -776,14 +771,14 @@ class DbDjango(DbWriteBase, DbReadBase): def make_place(self, place): if self.use_db_cache and place.cache: - data = pickle.loads(base64.decodestring(place.cache)) + data = place.from_cache() else: data = self.dji.get_place(place) return Place.create(data) def make_media(self, media): if self.use_db_cache and media.cache: - data = pickle.loads(base64.decodestring(media.cache)) + data = media.from_cache() else: data = self.dji.get_media(media) return MediaObject.create(data) @@ -1348,3 +1343,17 @@ class DbDjango(DbWriteBase, DbReadBase): # Next we add the links: self.dji.update_publics() + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + diff --git a/gramps/webapp/grampsdb/models.py b/gramps/webapp/grampsdb/models.py index 518dc65c8..2e75f63bb 100644 --- a/gramps/webapp/grampsdb/models.py +++ b/gramps/webapp/grampsdb/models.py @@ -38,6 +38,13 @@ from gramps.gen.utils.id import create_id, create_uid from gramps.webapp.grampsdb.profile import Profile from gramps.gen.constfunc import cuni +import sys +if sys.version_info[0] < 3: + import cPickle as pickle +else: + import pickle +import base64 + #--------------------------------------------------------------------------- # # Support functions @@ -424,6 +431,8 @@ class Tag(models.Model): name = models.TextField('name') color = models.CharField(max_length=13, blank=True, null=True) # "#000000000000" # Black priority = models.IntegerField('priority', blank=True, null=True) + cache = models.TextField(blank=True, null=True) + dji = None def __unicode__(self): return cuni(self.name) @@ -434,6 +443,34 @@ class Tag(models.Model): def get_link(self): return cuni("%s") % (self.get_url(), self.name) + def make_cache(self): + from gramps.webapp.libdjango import DjangoInterface + if self.dji is None: + self.dji = DjangoInterface() + raw = self.dji.get_tag(self) + return base64.encodestring(pickle.dumps(raw)) + + def from_cache(self): + return pickle.loads(base64.decodestring(self.cache)) + + def save_cache(self): + cache = self.make_cache() + if cache != self.cache: + self.cache = cache + models.Model.save(self) + + def save(self, *args, **kwargs): + save_cache = True + if "save_cache" in kwargs: + save_cache = kwargs["save_cache"] + del kwargs["save_cache"] + if not save_cache: + self.cache = "" + else: + self.cache = self.make_cache() + models.Model.save(self, *args, **kwargs) # save to db + + # Just the following have tag lists: # --------------------------------- #src/gen/lib/family.py @@ -461,6 +498,7 @@ class PrimaryObject(models.Model): #attributes = models.ManyToManyField("Attribute", blank=True, null=True) cache = models.TextField(blank=True, null=True) tags = models.ManyToManyField('Tag', blank=True, null=True) + dji = None def __unicode__(self): return cuni("%s: %s") % (self.__class__.__name__, @@ -473,6 +511,55 @@ class PrimaryObject(models.Model): def get_tag_list(self): return tuple([tag.handle for tag in self.tags.all()]) + def make_cache(self): + from gramps.webapp.libdjango import DjangoInterface + if self.dji is None: + self.dji = DjangoInterface() + + if isinstance(self, Person): + raw = self.dji.get_person(self) + elif isinstance(self, Family): + raw = self.dji.get_family(self) + elif isinstance(self, Place): + raw = self.dji.get_place(self) + elif isinstance(self, Media): + raw = self.dji.get_media(self) + elif isinstance(self, Source): + raw = self.dji.get_source(self) + elif isinstance(self, Citation): + raw = self.dji.get_citation(self) + elif isinstance(self, Repository): + raw = self.dji.get_repository(self) + elif isinstance(self, Note): + raw = self.dji.get_note(self) + elif isinstance(self, Event): + raw = self.dji.get_event(self) + elif isinstance(self, Tag): + raw = self.dji.get_tag(self) + else: + raise Exception("Don't know how to get raw '%s'" % type(item)) + return base64.encodestring(pickle.dumps(raw)) + + def from_cache(self): + return pickle.loads(base64.decodestring(self.cache)) + + def save_cache(self): + cache = self.make_cache() + if cache != self.cache: + self.cache = cache + models.Model.save(self) + + def save(self, *args, **kwargs): + save_cache = True + if "save_cache" in kwargs: + save_cache = kwargs["save_cache"] + del kwargs["save_cache"] + if not save_cache: + self.cache = "" + else: + self.cache = self.make_cache() + models.Model.save(self, *args, **kwargs) # save to db + class MyFamilies(models.Model): person = models.ForeignKey("Person") family = models.ForeignKey("Family") diff --git a/gramps/webapp/grampsdb/view/citation.py b/gramps/webapp/grampsdb/view/citation.py index 51643894c..a0f58f2e6 100644 --- a/gramps/webapp/grampsdb/view/citation.py +++ b/gramps/webapp/grampsdb/view/citation.py @@ -70,7 +70,7 @@ def process_citation(request, context, handle, act, add_to=None): # view, edit, ref_handle = pickform.data["picklist"] ref_obj = Citation.objects.get(handle=ref_handle) dji.add_citation_ref_default(parent_obj, ref_obj) - dji.rebuild_cache(parent_obj) # rebuild cache + parent_obj.save_cache() # rebuild cache return redirect("/%s/%s%s#tab-citations" % (item, handle, build_search(request))) else: context["pickform"] = pickform @@ -98,7 +98,6 @@ def process_citation(request, context, handle, act, add_to=None): # view, edit, if citationform.is_valid(): update_last_changed(citation, request.user.username) citation = citationform.save() - dji.rebuild_cache(citation) act = "view" else: act = "edit" @@ -114,15 +113,15 @@ def process_citation(request, context, handle, act, add_to=None): # view, edit, source = sourceform.save() citation.source = source update_last_changed(citation, request.user.username) - citation = citationform.save() - dji.rebuild_cache(source) - dji.rebuild_cache(citation) + citation = citationform.save(save_cache=False) + source.save_cache() + citation.save_cache() if add_to: item, handle = add_to model = dji.get_model(item) obj = model.objects.get(handle=handle) dji.add_citation_ref(obj, citation.handle) - dji.rebuild_cache(obj) + obj.save_cache() return redirect("/%s/%s#tab-citations" % (item, handle)) act = "view" else: diff --git a/gramps/webapp/grampsdb/view/event.py b/gramps/webapp/grampsdb/view/event.py index 714a8ccd6..4f50ea2e5 100644 --- a/gramps/webapp/grampsdb/view/event.py +++ b/gramps/webapp/grampsdb/view/event.py @@ -71,7 +71,6 @@ def delete_event(event): for person in people: recheck_birth_death_refs(person) person.save() - dji.rebuild_cache(person) def check_event(event): obj_type = ContentType.objects.get_for_model(Person) @@ -90,7 +89,6 @@ def check_event(event): continue recheck_birth_death_refs(person) person.save() - dji.rebuild_cache(person) def recheck_birth_death_refs(person): """ @@ -167,8 +165,8 @@ def process_event(request, context, handle, act, add_to=None): # view, edit, sav dji.add_event_ref_default(parent_obj, ref_obj) if item == "person": # then need to recheck birth/death indexes: recheck_birth_death_refs(parent_obj) - parent_obj.save() - dji.rebuild_cache(parent_obj) # rebuild cache + parent_obj.save(save_cache=False) + parent_obj.save_cache() return redirect("/%s/%s%s#tab-events" % (item, handle, build_search(request))) else: context["pickform"] = pickform @@ -198,7 +196,6 @@ def process_event(request, context, handle, act, add_to=None): # view, edit, sav # birth/death issues changed: check_event(event) event.save() - dji.rebuild_cache(event) act = "view" else: act = "edit" @@ -209,7 +206,6 @@ def process_event(request, context, handle, act, add_to=None): # view, edit, sav if eventform.is_valid(): update_last_changed(event, request.user.username) event = eventform.save() - dji.rebuild_cache(event) if add_to: item, handle = add_to model = dji.get_model(item) @@ -217,8 +213,8 @@ def process_event(request, context, handle, act, add_to=None): # view, edit, sav dji.add_event_ref_default(obj, event) if item == "person": # then need to recheck birth/death indexes: recheck_birth_death_refs(obj) - obj.save() - dji.rebuild_cache(obj) + obj.save(save_cache=False) + obj.save_cache() return redirect("/%s/%s#tab-events" % (item, handle)) act = "view" else: diff --git a/gramps/webapp/grampsdb/view/family.py b/gramps/webapp/grampsdb/view/family.py index bc63a695e..1f1eaeab9 100644 --- a/gramps/webapp/grampsdb/view/family.py +++ b/gramps/webapp/grampsdb/view/family.py @@ -87,8 +87,6 @@ def process_family(request, context, handle, act, add_to=None): # view, edit, sa pfo.save() ref_obj.save() person.save() - dji.rebuild_cache(person) # rebuild child - dji.rebuild_cache(ref_obj) # rebuild cache return redirect("/%s/%s%s#tab-references" % ("person", handle, build_search(request))) else: context["pickform"] = pickform @@ -150,7 +148,6 @@ def process_family(request, context, handle, act, add_to=None): # view, edit, sa order=len(familyform.cleaned_data["father"].families.all())+1) pfo.save() familyform.save() - dji.rebuild_cache(family) act = "view" else: act = "edit" @@ -173,7 +170,7 @@ def process_family(request, context, handle, act, add_to=None): # view, edit, sa pfo = MyFamilies(person=family.father, family=family, order=len(family.father.families.all())+1) pfo.save() - dji.rebuild_cache(family) + family.save_cache() if add_to: # add child or spouse to family item, handle = add_to person = Person.objects.get(handle=handle) @@ -186,7 +183,6 @@ def process_family(request, context, handle, act, add_to=None): # view, edit, sa #elif item == "spouse": # already added by selecting person.save() - dji.rebuild_cache(person) # rebuild child return redirect("/%s/%s%s#tab-references" % ("person", handle, build_search(request))) act = "view" else: diff --git a/gramps/webapp/grampsdb/view/media.py b/gramps/webapp/grampsdb/view/media.py index cd83f0202..fc4234031 100644 --- a/gramps/webapp/grampsdb/view/media.py +++ b/gramps/webapp/grampsdb/view/media.py @@ -88,7 +88,7 @@ def process_media(request, context, handle, act, add_to=None): # view, edit, sav ref_handle = pickform.data["picklist"] ref_obj = Media.objects.get(handle=ref_handle) dji.add_media_ref_default(parent_obj, ref_obj) - dji.rebuild_cache(parent_obj) # rebuild cache + parent_obj.save_cache() # rebuild cache return redirect("/%s/%s%s#tab-media" % (item, handle, build_search(request))) else: context["pickform"] = pickform @@ -167,7 +167,6 @@ def process_media(request, context, handle, act, add_to=None): # view, edit, sav if mediaform.is_valid(): update_last_changed(media, request.user.username) media = mediaform.save() - dji.rebuild_cache(media) act = "view" else: act = "edit" @@ -177,16 +176,17 @@ def process_media(request, context, handle, act, add_to=None): # view, edit, sav mediaform.model = media if mediaform.is_valid(): update_last_changed(media, request.user.username) - media = mediaform.save() + media = mediaform.save(save_cache=False) if add_to: item, handle = add_to model = dji.get_model(item) obj = model.objects.get(handle=handle) dji.add_media_ref_default(obj, media) - dji.rebuild_cache(obj) + obj.save_cache() + media.save_cache() return redirect("/%s/%s#tab-gallery" % (item, handle)) else: - dji.rebuild_cache(media) + media.save_cache() act = "view" else: act = "add" diff --git a/gramps/webapp/grampsdb/view/note.py b/gramps/webapp/grampsdb/view/note.py index aefea7891..3ac1c29d8 100644 --- a/gramps/webapp/grampsdb/view/note.py +++ b/gramps/webapp/grampsdb/view/note.py @@ -80,7 +80,7 @@ def process_note(request, context, handle, act, add_to=None): # view, edit, save ref_handle = pickform.data["picklist"] ref_obj = Note.objects.get(handle=ref_handle) dji.add_note_ref(parent_obj, ref_obj) - dji.rebuild_cache(parent_obj) # rebuild cache + parent_obj.save_cache() # rebuild cache return redirect("/%s/%s%s#tab-notes" % (item, handle, build_search(request))) else: context["pickform"] = pickform @@ -109,7 +109,7 @@ def process_note(request, context, handle, act, add_to=None): # view, edit, save note.text = notedata[0] note = noteform.save() dji.save_note_markup(note, notedata[1]) - dji.rebuild_cache(note) + note.save_cache() notetext = noteform.data["notetext"] act = "view" else: @@ -126,13 +126,13 @@ def process_note(request, context, handle, act, add_to=None): # view, edit, save note.text = notedata[0] note = noteform.save() dji.save_note_markup(note, notedata[1]) - dji.rebuild_cache(note) + note.save_cache() if add_to: item, handle = add_to model = dji.get_model(item) obj = model.objects.get(handle=handle) dji.add_note_ref(obj, note) - dji.rebuild_cache(obj) + obj.save_cache() return redirect("/%s/%s#tab-notes" % (item, handle)) notetext = noteform.data["notetext"] act = "view" diff --git a/gramps/webapp/grampsdb/view/person.py b/gramps/webapp/grampsdb/view/person.py index 849ff1edb..49c82af6d 100644 --- a/gramps/webapp/grampsdb/view/person.py +++ b/gramps/webapp/grampsdb/view/person.py @@ -167,7 +167,7 @@ def process_surname(request, handle, order, sorder, act="view"): surname = sf.save(commit=False) check_primary(surname, surnames) surname.save() - dji.rebuild_cache(person) + person.save_cache() return redirect("/person/%s/name/%s/surname/%s%s#tab-surnames" % (person.handle, name.order, sorder, build_search(request))) @@ -182,7 +182,7 @@ def process_surname(request, handle, order, sorder, act="view"): surname = sf.save(commit=False) check_primary(surname, name.surname_set.all().exclude(order=surname.order)) surname.save() - dji.rebuild_cache(person) + person.save_cache() return redirect("/person/%s/name/%s/surname/%s%s#tab-surnames" % (person.handle, name.order, sorder, build_search(request))) @@ -281,7 +281,7 @@ def process_name(request, handle, order, act="view"): surname.prefix = sf.cleaned_data["prefix"] if sf.cleaned_data["prefix"] != " prefix " else "" surname.primary = True # FIXME: why is this False? Remove from form? surname.save() - dji.rebuild_cache(person) + person.save_cache() return redirect("/person/%s/name/%s%s#tab-surnames" % (person.handle, name.order, build_search(request))) else: @@ -317,7 +317,7 @@ def process_name(request, handle, order, act="view"): surname.prefix = sf.cleaned_data["prefix"] if sf.cleaned_data["prefix"] != " prefix " else "" surname.primary = True # FIXME: why is this False? Remove from form? surname.save() - dji.rebuild_cache(person) + person.save_cache() return redirect("/person/%s/name/%s%s#tab-surnames" % (person.handle, name.order, build_search(request))) else: @@ -373,8 +373,8 @@ def process_person(request, context, handle, act, add_to=None): # view, edit, sa pfo = MyParentFamilies(person=person, family=obj, order=len(person.parent_families.all())+1) pfo.save() - dji.rebuild_cache(person) # rebuild child - dji.rebuild_cache(obj) # rebuild family + person.save_cache() # rebuild child + obj.save_cache() # rebuild family return redirect("/%s/%s%s" % (item, handle, build_search(request))) else: context["pickform"] = pickform @@ -447,10 +447,10 @@ def process_person(request, context, handle, act, add_to=None): # view, edit, sa pfo = MyParentFamilies(person=person, family=obj, order=len(person.parent_families.all())+1) pfo.save() - dji.rebuild_cache(person) # rebuild child - dji.rebuild_cache(obj) # rebuild family + person.save_cache() # rebuild child + obj.save_cache() # rebuild family return redirect("/%s/%s%s" % (item, handle, build_search(request))) - dji.rebuild_cache(person) + person.save_cache() return redirect("/person/%s%s" % (person.handle, build_search(request))) else: # need to edit again diff --git a/gramps/webapp/grampsdb/view/place.py b/gramps/webapp/grampsdb/view/place.py index 052c6dc94..b4ea60ffb 100644 --- a/gramps/webapp/grampsdb/view/place.py +++ b/gramps/webapp/grampsdb/view/place.py @@ -64,7 +64,6 @@ def process_place(request, context, handle, act, add_to=None): # view, edit, sav if placeform.is_valid(): update_last_changed(place, request.user.username) place = placeform.save() - dji.rebuild_cache(place) act = "view" else: act = "edit" @@ -75,13 +74,12 @@ def process_place(request, context, handle, act, add_to=None): # view, edit, sav if placeform.is_valid(): update_last_changed(place, request.user.username) place = placeform.save() - dji.rebuild_cache(place) if add_to: item, handle = add_to model = dji.get_model(item) obj = model.objects.get(handle=handle) dji.add_place_ref(obj, place.handle) - dji.rebuild_cache(obj) + obj.save_cache() return redirect("/%s/%s#tab-places" % (item, handle)) act = "view" else: diff --git a/gramps/webapp/grampsdb/view/repository.py b/gramps/webapp/grampsdb/view/repository.py index 95410a1eb..25cbd74ea 100644 --- a/gramps/webapp/grampsdb/view/repository.py +++ b/gramps/webapp/grampsdb/view/repository.py @@ -70,7 +70,7 @@ def process_repository(request, context, handle, act, add_to=None): # view, edit ref_handle = pickform.data["picklist"] ref_obj = Repository.objects.get(handle=ref_handle) dji.add_repository_ref_default(parent_obj, ref_obj) - dji.rebuild_cache(parent_obj) # rebuild cache + parent_obj.save_cache() # rebuild cache return redirect("/%s/%s%s#tab-repositories" % (item, handle, build_search(request))) else: context["pickform"] = pickform @@ -92,7 +92,6 @@ def process_repository(request, context, handle, act, add_to=None): # view, edit if repositoryform.is_valid(): update_last_changed(repository, request.user.username) repository = repositoryform.save() - dji.rebuild_cache(repository) act = "view" else: act = "edit" @@ -103,13 +102,12 @@ def process_repository(request, context, handle, act, add_to=None): # view, edit if repositoryform.is_valid(): update_last_changed(repository, request.user.username) repository = repositoryform.save() - dji.rebuild_cache(repository) if add_to: item, handle = add_to model = dji.get_model(item) obj = model.objects.get(handle=handle) dji.add_repository_ref_default(obj, repository) - dji.rebuild_cache(obj) + obj.save_cache() return redirect("/%s/%s#tab-repositories" % (item, handle)) act = "view" else: diff --git a/gramps/webapp/grampsdb/view/source.py b/gramps/webapp/grampsdb/view/source.py index b0e178f05..278c9f67f 100644 --- a/gramps/webapp/grampsdb/view/source.py +++ b/gramps/webapp/grampsdb/view/source.py @@ -70,7 +70,7 @@ def process_source(request, context, handle, act, add_to=None): # view, edit, sa ref_handle = pickform.data["picklist"] ref_obj = Source.objects.get(handle=ref_handle) dji.add_source_ref_default(parent_obj, ref_obj) - dji.rebuild_cache(parent_obj) # rebuild cache + parent_obj.save_cache() # rebuild cache return redirect("/%s/%s%s#tab-sources" % (item, handle, build_search(request))) else: context["pickform"] = pickform diff --git a/gramps/webapp/grampsdb/view/tag.py b/gramps/webapp/grampsdb/view/tag.py index ffd743b2a..924e90b2a 100644 --- a/gramps/webapp/grampsdb/view/tag.py +++ b/gramps/webapp/grampsdb/view/tag.py @@ -79,7 +79,7 @@ def process_tag(request, context, handle, act, add_to=None): # view, edit, save model = dji.get_model(item) obj = model.objects.get(handle=handle) dji.add_tag_ref_default(obj, tag) - dji.rebuild_cache(obj) + obj.save_cache() return redirect("/%s/%s#tab-tags" % (item, handle)) act = "view" else: diff --git a/gramps/webapp/grampsdb/views.py b/gramps/webapp/grampsdb/views.py index c77b3f169..f05f51645 100644 --- a/gramps/webapp/grampsdb/views.py +++ b/gramps/webapp/grampsdb/views.py @@ -274,8 +274,8 @@ def process_report_run(request, handle): # just give it, perhaps in a new tab from django.http import HttpResponse response = HttpResponse(mimetype="text/html") - content = "".join(open(filename).readlines()) - response._set_content(content) + for line in open(filename): + response.write(line) return response else: return send_file(request, filename, mimetype) @@ -1474,9 +1474,15 @@ def process_json_request(request): """ Process an Ajax/Json query request. """ + import gramps.gen.lib + from gramps.gen.proxy import PrivateProxyDb, LivingProxyDb + db = DbDjango() if not request.user.is_authenticated(): - response_data = {"results": [], "total": 0} - return HttpResponse(simplejson.dumps(response_data), mimetype="application/json") + db = PrivateProxyDb(db) + db = LivingProxyDb(db, + LivingProxyDb.MODE_INCLUDE_LAST_NAME_ONLY, + None, # current year + 1) # years after death field = request.GET.get("field", None) query = request.GET.get("q", "") page = int(request.GET.get("p", "1")) @@ -1485,40 +1491,30 @@ def process_json_request(request): q, order, terms = build_person_query(request, query) q &= Q(person__gender_type__name="Female") matches = Name.objects.filter(q).order_by(*order) - response_data = {"results": [], "total": len(matches)} - for match in matches[(page - 1) * size:page * size]: - response_data["results"].append( - {"id": match.person.handle, - "name": match.get_selection_string(), - }) + class_type = gramps.gen.lib.Person + handle_expr = "match.person.handle" elif field == "father": q, order, terms = build_person_query(request, query) q &= Q(person__gender_type__name="Male") matches = Name.objects.filter(q).order_by(*order) - response_data = {"results": [], "total": len(matches)} - for match in matches[(page - 1) * size:page * size]: - response_data["results"].append( - {"id": match.person.handle, - "name": match.get_selection_string(), - }) + class_type = gramps.gen.lib.Person + handle_expr = "match.person.handle" elif field == "person": q, order, terms = build_person_query(request, query) matches = Name.objects.filter(q).order_by(*order) - response_data = {"results": [], "total": len(matches)} - for match in matches[(page - 1) * size:page * size]: - response_data["results"].append( - {"id": match.person.handle, - "name": match.get_selection_string(), - }) + class_type = gramps.gen.lib.Person + handle_expr = "match.person.handle" elif field == "place": q, order, terms = build_place_query(request, query) matches = Place.objects.filter(q).order_by(*order) - response_data = {"results": [], "total": len(matches)} - for match in matches[(page - 1) * size:page * size]: - response_data["results"].append( - {"id": match.handle, - "name": match.get_selection_string(), - }) + class_type = gramps.gen.lib.Place + handle_expr = "match.handle" else: raise Exception("""Invalid field: '%s'; Example: /json/?field=mother&q=Smith&p=1&size=10""" % field) + ## ------------ + response_data = {"results": [], "total": len(matches)} + for match in matches[(page - 1) * size:page * size]: + obj = db.get_from_name_and_handle(class_type.__name__, eval(handle_expr)) + if obj: + response_data["results"].append(obj.to_struct()) return HttpResponse(simplejson.dumps(response_data), mimetype="application/json") diff --git a/gramps/webapp/init.py b/gramps/webapp/init.py index b581c3179..18a17a3bb 100644 --- a/gramps/webapp/init.py +++ b/gramps/webapp/init.py @@ -151,7 +151,7 @@ for table, entries in [("grampsdb.config", (("name", '"Gramps package (portable XML) Import"'), ('gramps_id', '"R0019"'), ("handle", '"im_gpkg"'), - ("options", '"iff=gramps\\ni=http://sourceforge.net/p/gramps/source/ci/master/tree/example/gramps/example.gramps"'), + ("options", '"iff=gramps\\ni=http://sourceforge.net/p/gramps/source/ci/master/tree/example/gramps/example.gramps?format=raw"'), ("report_type", '"import"')), ])]: entry_count = 0 diff --git a/gramps/webapp/libdjango.py b/gramps/webapp/libdjango.py index 9b0703e12..ec0f012c8 100644 --- a/gramps/webapp/libdjango.py +++ b/gramps/webapp/libdjango.py @@ -64,6 +64,13 @@ from gramps.gen.utils.file import fix_encoding # OR # gperson = dbdjango.DbDjango().get_person_from_handle(handle) +def check_diff(item, raw): + encoded = base64.encodestring(pickle.dumps(raw)) + if item.cache != encoded: + print("Different:", item.__class__.__name__, item.gramps_id) + print("raw :", raw) + print("cache:", item.from_cache()) + #------------------------------------------------------------------------- # # Import functions @@ -922,7 +929,7 @@ class DjangoInterface(object): last_changed=todate(changed), confidence=confidence, page=page) - citation.save() + citation.save(save_cache=False) def add_citation_detail(self, citation_data): (handle, gid, date, page, confidence, source_handle, note_list, @@ -941,12 +948,12 @@ class DjangoInterface(object): return citation.source = source self.add_date(citation, date) - citation.cache = self.encode_raw(citation_data) - citation.save() + citation.save(save_cache=False) self.add_note_list(citation, note_list) self.add_media_ref_list(citation, media_list) self.add_citation_attribute_list(citation, attribute_list) self.add_tag_list(citation, tag_list) + citation.save_cache() def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False): object_type = ContentType.objects.get_for_model(obj) # obj is family @@ -1210,8 +1217,10 @@ class DjangoInterface(object): str(parent_family_handle)), file=sys.stderr) return #person.parent_families.add(family) - pfo = models.MyParentFamilies(person=person, family=family, - order=len(models.MyParentFamilies.objects.filter(person=person)) + 1) + pfo = models.MyParentFamilies( + person=person, + family=family, + order=len(models.MyParentFamilies.objects.filter(person=person)) + 1) pfo.save() person.save() @@ -1309,8 +1318,7 @@ class DjangoInterface(object): last_changed=todate(change), private=private, gender_type=models.get_type(models.GenderType, gender)) - #person.cache = base64.encodestring(cPickle.dumps(data)) - person.save() + person.save(save_cache=False) def add_person_detail(self, data): # Unpack from the BSDDB: @@ -1378,19 +1386,9 @@ class DjangoInterface(object): if events: person.death = events[0].ref_object person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events) - person.cache = self.encode_raw(data) person.save() return person - def add_note_detail(self, data): - # Unpack from the BSDDB: - (handle, gid, styled_text, format, note_type, - change, tag_list, private) = data - note = models.Note.objects.get(handle=handle) - note.cache = self.encode_raw(data) - note.save() - self.add_tag_list(note, tag_list) - def save_note_markup(self, note, markup_list): # delete any prexisting markup: models.Markup.objects.filter(note=note).delete() @@ -1419,10 +1417,18 @@ class DjangoInterface(object): preformatted=format, text=text, note_type=models.get_type(models.NoteType, note_type)) - #n.cache = base64.encodestring(cPickle.dumps(data)) - n.save() + n.save(save_cache=False) self.save_note_markup(n, markup_list) + def add_note_detail(self, data): + # Unpack from the BSDDB: + (handle, gid, styled_text, format, note_type, + change, tag_list, private) = data + note = models.Note.objects.get(handle=handle) + note.save(save_cache=False) + self.add_tag_list(note, tag_list) + note.save_cache() + def add_family(self, data): # Unpack from the BSDDB: (handle, gid, father_handle, mother_handle, @@ -1434,8 +1440,7 @@ class DjangoInterface(object): family_rel_type = models.get_type(models.FamilyRelType, the_type), last_changed=todate(change), private=private) - #family.cache = base64.encodestring(cPickle.dumps(data)) - family.save() + family.save(save_cache=False) def add_family_detail(self, data): # Unpack from the BSDDB: @@ -1465,8 +1470,7 @@ class DjangoInterface(object): print(("ERROR: Mother does not exist: '%s'" % str(mother_handle)), file=sys.stderr) family.mother = None - family.cache = self.encode_raw(data) - family.save() + family.save(save_cache=False) self.add_child_ref_list(family, child_ref_list) self.add_note_list(family, note_list) self.add_attribute_list(family, attribute_list) @@ -1475,6 +1479,7 @@ class DjangoInterface(object): self.add_event_ref_list(family, event_ref_list) self.add_lds_list("family", family, lds_seal_list) self.add_tag_list(family, tag_list) + family.save_cache() def add_source(self, data): (handle, gid, title, @@ -1489,8 +1494,7 @@ class DjangoInterface(object): source = models.Source(handle=handle, gramps_id=gid, title=title, author=author, pubinfo=pubinfo, abbrev=abbrev, last_changed=todate(change), private=private) - #source.cache = base64.encodestring(cPickle.dumps(data)) - source.save() + source.save(save_cache=False) def add_source_detail(self, data): (handle, gid, title, @@ -1508,13 +1512,13 @@ class DjangoInterface(object): print(("ERROR: Source does not exist: '%s'" % str(handle)), file=sys.stderr) return - source.cache = self.encode_raw(data) - source.save() + source.save(save_cache=False) self.add_note_list(source, note_list) self.add_media_ref_list(source, media_list) self.add_source_attribute_list(source, attribute_list) self.add_repository_ref_list(source, reporef_list) self.add_tag_list(source, tag_list) + source.save_cache() def add_repository(self, data): (handle, gid, the_type, name, note_list, @@ -1526,8 +1530,7 @@ class DjangoInterface(object): private=private, repository_type=models.get_type(models.RepositoryType, the_type), name=name) - #repository.cache = base64.encodestring(cPickle.dumps(data)) - repository.save() + repository.save(save_cache=False) def add_repository_detail(self, data): (handle, gid, the_type, name, note_list, @@ -1538,12 +1541,12 @@ class DjangoInterface(object): print(("ERROR: Repository does not exist: '%s'" % str(handle)), file=sys.stderr) return - repository.cache = self.encode_raw(data) - repository.save() + repository.save(save_cache=False) self.add_note_list(repository, note_list) self.add_url_list("repository", repository, url_list) self.add_address_list("repository", repository, address_list) self.add_tag_list(repository, tag_list) + repository.save_cache() def add_location(self, field, obj, location_data, order): # location now has 8 items @@ -1607,7 +1610,10 @@ class DjangoInterface(object): code=code, last_changed=todate(change), private=private) - place.save() + try: + place.save(save_cache=False) + except: + print("FIXME: error in saving place") def add_place_detail(self, data): (handle, gid, title, long, lat, @@ -1629,8 +1635,7 @@ class DjangoInterface(object): print(("ERROR: Place does not exist: '%s'" % str(handle)), file=sys.stderr) return - place.cache = self.encode_raw(data) - place.save() + place.save(save_cache=False) self.add_url_list("place", place, url_list) self.add_media_ref_list(place, media_list) self.add_citation_list(place, citation_list) @@ -1641,16 +1646,7 @@ class DjangoInterface(object): for loc_data in alt_location_list: self.add_location("place", place, loc_data, count) count + 1 - - def add_tag_detail(self, data): - (handle, - name, - color, - priority, - change) = data - tag = models.Tag.objects.get(handle=handle) - tag.cache = self.encode_raw(data) - tag.save() + place.save_cache() def add_tag(self, data): (handle, @@ -1664,8 +1660,17 @@ class DjangoInterface(object): color=color, priority=priority, last_changed=todate(change)) - tag.save() + tag.save(save_cache=False) + def add_tag_detail(self, data): + (handle, + name, + color, + priority, + change) = data + tag = models.Tag.objects.get(handle=handle) + tag.save() + def add_media(self, data): (handle, gid, path, mime, desc, checksum, @@ -1680,9 +1685,8 @@ class DjangoInterface(object): path=path, mime=mime, checksum=checksum, desc=desc, last_changed=todate(change), private=private) - #media.cache = base64.encodestring(cPickle.dumps(data)) self.add_date(media, date) - media.save() + media.save(save_cache=False) def add_media_detail(self, data): (handle, gid, path, mime, desc, @@ -1700,12 +1704,12 @@ class DjangoInterface(object): print(("ERROR: Media does not exist: '%s'" % str(handle)), file=sys.stderr) return - media.cache = self.encode_raw(data) - media.save() + media.save(save_cache=False) self.add_note_list(media, note_list) self.add_citation_list(media, citation_list) self.add_attribute_list(media, attribute_list) self.add_tag_list(media, tag_list) + media.save_cache() def add_event(self, data): (handle, gid, the_type, date, description, place_handle, @@ -1717,9 +1721,8 @@ class DjangoInterface(object): private=private, description=description, last_changed=todate(change)) - #event.cache = base64.encodestring(cPickle.dumps(data)) self.add_date(event, date) - event.save() + event.save(save_cache=False) def add_event_detail(self, data): (handle, gid, the_type, date, description, place_handle, @@ -1738,13 +1741,13 @@ class DjangoInterface(object): print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr) event.place = place - event.cache = self.encode_raw(data) - event.save() + event.save(save_cache=False) self.add_note_list(event, note_list) self.add_attribute_list(event, attribute_list) self.add_media_ref_list(event, media_list) self.add_citation_list(event, citation_list) self.add_tag_list(event, tag_list) + event.save_cache() def get_raw(self, item): """ @@ -1772,101 +1775,6 @@ class DjangoInterface(object): raise Exception("Don't know how to get raw '%s'" % type(item)) return raw - def reset_cache(self, item): - """ - Resets the cache version of an object, but doesn't save it to the database. - """ - item.cache = self.get_cache(item) - - def encode_raw(self, raw): - return base64.encodestring(pickle.dumps(raw)) - - def get_cache(self, item): - """ - Gets the cache version of an object. - """ - raw = self.get_raw(item) - return base64.encodestring(pickle.dumps(raw)) - - def rebuild_cache(self, item): - """ - Resets the cache version of an object, and saves it to the database. - """ - self.update_public(item, save=False) - self.reset_cache(item) - item.save() - - @transaction.commit_on_success - def rebuild_caches(self, callback=None): - """ - Call this to rebuild the caches for all primary models. - """ - if not isinstance(callback, collections.Callable): - callback = lambda percent: None # dummy - - callback(0) - count = 0.0 - total = (self.Note.all().count() + - self.Person.all().count() + - self.Event.all().count() + - self.Family.all().count() + - self.Repository.all().count() + - self.Place.all().count() + - self.Media.all().count() + - self.Source.all().count() + - self.Citation.all().count() + - self.Tag.all().count()) - - for item in self.Note.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Person.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Family.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Source.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Event.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Repository.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Place.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Media.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Citation.all(): - self.rebuild_cache(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Tag.all(): - self.rebuild_cache(item) - count += 1 - callback(100) - def check_caches(self, callback=None): """ Call this to check the caches for all primary models. @@ -1889,71 +1797,62 @@ class DjangoInterface(object): for item in self.Note.all(): raw = self.get_note(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Person.all(): raw = self.get_person(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Family.all(): raw = self.get_family(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Source.all(): raw = self.get_source(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Event.all(): raw = self.get_event(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Repository.all(): raw = self.get_repository(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Place.all(): raw = self.get_place(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Media.all(): raw = self.get_media(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) + encoded = base64.encodestring(pickle.dumps(raw)) count += 1 callback(100 * (count/total if total else 0)) for item in self.Citation.all(): raw = self.get_citation(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100 * (count/total if total else 0)) for item in self.Tag.all(): raw = self.get_tag(item) - if item.cache == base64.encodestring(pickle.dumps(raw)): - print("Different!", item) + check_diff(item, raw) count += 1 callback(100) @@ -2066,11 +1965,20 @@ class DjangoInterface(object): if obj.public != public: obj.public = public if save: + print("Updating public:", obj.__class__.__name__, obj.gramps_id) obj.save() + #log = self.Log() + #log.referenced_by = obj + #log.object_id = obj.id + #log.object_type = obj_type + #log.log_type = "update public status" + #log.reason = reason + #log.order = 0 + #log.save() def update_publics(self, callback=None): """ - Call this to check the caches for all primary models. + Call this to update probably_alive for all primary models. """ if not isinstance(callback, collections.Callable): callback = lambda percent: None # dummy diff --git a/gramps/webapp/reports.py b/gramps/webapp/reports.py index 9601be855..d72b183e6 100644 --- a/gramps/webapp/reports.py +++ b/gramps/webapp/reports.py @@ -92,10 +92,6 @@ def import_file(db, filename, user): db.prepare_import() retval = import_function(db, filename, user) db.commit_import() - # FIXME: need to call probably_alive - for person in Person.objects.all(): - person.probably_alive = not bool(person.death) - person.save() return retval return False diff --git a/gramps/webapp/utils.py b/gramps/webapp/utils.py index 36abc5a06..6b588c275 100644 --- a/gramps/webapp/utils.py +++ b/gramps/webapp/utils.py @@ -364,13 +364,13 @@ def make_image_button2(button, text, url, kwargs="", last=""): elif button == "?": # edit filename = "/images/text-editor.png" elif button == "add child to existing family": - filename = "/images/scalable/gramps-parents-open.svg" + filename = "/images/gramps-parents-open.png" elif button == "add child to new family": - filename = "/images/scalable/gramps-parents-add.svg" + filename = "/images/gramps-parents-add.png" elif button == "add spouse to existing family": - filename = "/images/scalable/add-parent-existing-family.svg" + filename = "/images/add-parent-existing-family.png" elif button == "add spouse to new family": - filename = "/images/scalable/gramps-parents.svg" + filename = "/images/gramps-parents.png" return cuni("""%s""") % (text, text, filename, url, kwargs, last) def event_table(obj, user, act, url, args): diff --git a/images/add-parent-existing-family.png b/images/add-parent-existing-family.png new file mode 100644 index 0000000000000000000000000000000000000000..5b5d4a0ce6332cd957b882fcd16fb2a024acc86b GIT binary patch literal 1593 zcmV-92FCe`P) zO086NFWMrY>Q!&jB9M@ff&_wy1|qj1&_pExEJGW}sYx6k*N64a&d$uv%zx@l4Q?=~ zw|y^v{onh1`v33K3;e%Jy7iz|gB(t#A9HNyn~J8S387IKc*mOy;ZuK{1X;iLw_0hh zuIfM2bjzYFl2{TwJN~Q2%byFV9fC^pg%hK@1`lP^x=xcAL6o5Bb=d5}>N`=;{Oa!} z5S;%_Pc7TK>qoiLwr;^9h}ehUs<+pc<|aCH{?U7CpzEH>M!*;LIKSPyd-yw*f~n;k z3%ag>GXfHavYmFSUP!h+`K^rFHL&mTTzPvzv5JsY3xY+EY1^sJ{r(yb9{w z1naDVB_04k2rz&}>%Qv7C-Qv%svp@yBxyp^R4~R7b|NGRfo^5st$MSeFI*mbxAPKO_FKvFY_GlD_tkb!^9;fp08#T&>;Yn6!V6erXEz z)P9UV@B?fe+#%(Q!`krhh$%aH>D3Q3>@2Q9H%th{#?6+F(~GxB$J<8+wCY{cYnLZJ z`WSHde)Xq2cMbfo6VIEPP2d{@{Ug6XZumizx1>?-t{`QlkjYlDrJTj&6^`NEcjMW= z6I2|AI)-rc`aw+V2N6?Yrww`B?EUiVjVlwY@*|HxDV9p#BiuC^qYwaWo8$7#n>es{ z5R&sH2mtNK#Nf6PvS}R$$NKQa-6hb{ICg&h*Jy41E>^@gEUkuUhY8G-T~$rxC;-SS zt~Qo&?Arq01ET<=0HuH(KaJ&Pj01O;Ftb{OWt5<6G5`Sy9QmwIBZkKp*!**2?eJyI8DGQK_+FO(Qp^s z*)tau08!}CbFvzaWt?u?G}F4GHI3pO{o<}ZiVx<|vwqyhyf|v+6DFvJg zOnl%WXLOJ`73vJOWUeh%q{!q%ByI$7`FDLz!-y}N*JgVld}`Az9>s-Ln69C=5cNb?A*vm-ymg#1wkk);h$%69Z2Xv|JCy0p&MFH9Y!_?IlUN|$u=ck@K zvGDdYKLOAN5N)=}|0T<@;_j~QhbbXCSVwBie~2qVzjXI#w{+>63q{lL++XK$?4{>P z!9N{EjhWwHzdrpo@JY~oE}*_pU#^tP{keQ@NL5wMR2UZLu8DW+UDAq6aO~8Fz|9wu zlHo>fvvuaR6DMn%nQxlO8{uR7#txJU`6HYo#~IUw5Q-#8OqOLLWN9sEwO%=Ye)1Fm r_CJUIXTbm%|M@cpApWe5Z`Hp6Z;$q9iB=#j00000NkvXXu0mjfy^rnC literal 0 HcmV?d00001 diff --git a/images/gramps-parents-add.png b/images/gramps-parents-add.png new file mode 100644 index 0000000000000000000000000000000000000000..23c669914ab5555826cdeb41c5ca962902eaf7fc GIT binary patch literal 1637 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