From 9b06e3aac6ad1eaa4bff75151a5393301672d558 Mon Sep 17 00:00:00 2001 From: Jan Philipp Timme Date: Mon, 29 Sep 2014 13:40:07 +0200 Subject: [PATCH] [TASK] Cleanup, begin implementing a mapping wizard for citavi persons. --- citavi_mapper/urls.py | 3 +- frontend/forms.py | 7 ++- frontend/models.py | 3 + frontend/templates/project/view-person.html | 37 +++++------- frontend/templates/projects.html | 2 +- frontend/views.py | 23 +++++++- service/Citavi.py | 64 +-------------------- service/Django.py | 6 +- service/Mapper.py | 39 ++++++------- 9 files changed, 69 insertions(+), 115 deletions(-) diff --git a/citavi_mapper/urls.py b/citavi_mapper/urls.py index 7860a9f..a53010f 100644 --- a/citavi_mapper/urls.py +++ b/citavi_mapper/urls.py @@ -8,7 +8,8 @@ admin.autodiscover() from frontend.views import login_wrap, logout_wrap from frontend.views import IndexView, ProjectView, ProjectUpdateView, ProjectPersonView, ProjectMapPersonView -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^$', IndexView.as_view(), name='frontend-index'), url(r'^project/$', ProjectView.as_view(), name='frontend-projects'), url(r'^project/(?P\d+)/update$', ProjectUpdateView.as_view(), name='frontend-project-update'), diff --git a/frontend/forms.py b/frontend/forms.py index 7bdcab4..01e992d 100644 --- a/frontend/forms.py +++ b/frontend/forms.py @@ -61,9 +61,10 @@ class PersonMapForm(forms.Form): self.helper.layout = Layout( 'action', 'global_identity', - Submit('continue', 'Continue', css_class='btn-default'), + Submit('skip', 'Skip', css_class='btn-default'), Submit('save-continue', 'Save and continue', css_class='btn-default'), ) - action = forms.ChoiceField(choices=[('new', 'Create new global Identity'), ('existing', 'Map to existing identity')], widget=forms.RadioSelect()) - global_identity = forms.ModelChoiceField(queryset=PersonGlobalIdentity.objects.all()) \ No newline at end of file + action = forms.ChoiceField(choices=[('new', 'Create new global Identity'), ('existing', 'Map to existing identity')], initial='new', widget=forms.RadioSelect()) + global_identity = forms.ModelChoiceField(queryset=PersonGlobalIdentity.objects.all(), required=False) + diff --git a/frontend/models.py b/frontend/models.py index 1014c15..972ec79 100644 --- a/frontend/models.py +++ b/frontend/models.py @@ -4,6 +4,7 @@ from django.db import models class Project(models.Model): + """ Model representing a citavi project. """ name = models.CharField(max_length=255) description = models.TextField() associated_filename = models.CharField(max_length=255, blank=True, null=True) @@ -18,6 +19,7 @@ class Project(models.Model): class PersonGlobalIdentity(models.Model): + """ Model representing a global person identity in django. Can be used to link any foreign identity to it. """ type = models.CharField(max_length=255) # TODO: Extend this for further stuff - maybe vivo external url or something? @@ -27,6 +29,7 @@ class PersonGlobalIdentity(models.Model): class CitaviProjectIdentity(models.Model): + """ Model representing an identity from a citavi project. """ global_identity = models.ForeignKey(PersonGlobalIdentity, blank=True, null=True) project = models.ForeignKey(Project, blank=False, null=False) citavi_uuid = models.CharField(max_length=255, blank=False, null=False) diff --git a/frontend/templates/project/view-person.html b/frontend/templates/project/view-person.html index 5c39c39..19b1d51 100644 --- a/frontend/templates/project/view-person.html +++ b/frontend/templates/project/view-person.html @@ -6,6 +6,7 @@
  • Back to projects
  • {% endblock %} {% block content %} +

    Project

    Name: {{project.name}}

    Description: {{project.description}}

    Current uploaded database filename: {{project.associated_filename}}

    @@ -15,7 +16,13 @@

    {% crispy form %}

    --> -

    List of unmapped citavi persons in project:

    +

    Stats

    +

    + Mapped: {{mapped_persons|length}}
    + Unmapped: {{unmapped_persons|length}}
    +

    + +

    List of unmapped citavi persons in project

    {% if not unmapped_persons %}

    No unmapped persons in this project. Congratulations!

    {% else %} @@ -55,39 +62,25 @@ {% endif %}
    -

    List of mapped citavi persons in project:

    +

    List of mapped citavi persons in project

    {% if not mapped_persons %}

    No mapped persons in this project. Work harder!

    {% else %} - - - - - - - - - + + - {% for person in mapped_persons.items %} + {% for person_id, person in mapped_persons.items %} - - - - - - - - - + + {% endfor %} diff --git a/frontend/templates/projects.html b/frontend/templates/projects.html index f0ba006..e37aeab 100644 --- a/frontend/templates/projects.html +++ b/frontend/templates/projects.html @@ -28,7 +28,7 @@ {% endfor %} diff --git a/frontend/views.py b/frontend/views.py index 377cfdb..eb9d7fe 100644 --- a/frontend/views.py +++ b/frontend/views.py @@ -21,6 +21,7 @@ FRONTEND_PAGE_NAME = 'Citavi Mapper' # Login wrapper functions def login_wrap(*args, **kwargs): + """ Wrapper function for login page. """ kwargs['extra_context'] = { 'page': { 'name': FRONTEND_PAGE_NAME, @@ -30,6 +31,7 @@ def login_wrap(*args, **kwargs): return login(*args, **kwargs) def logout_wrap(*args, **kwargs): + """ Wrapper function for logout page. """ kwargs['extra_context'] = { 'page': { 'name': FRONTEND_PAGE_NAME, @@ -41,6 +43,7 @@ def logout_wrap(*args, **kwargs): # My base classes for views class MyViewMixin(object): + """ Basic view mixin to add global variables to all templates. """ template_name = 'base.html' page_name = FRONTEND_PAGE_NAME page_title = 'BASE' @@ -70,6 +73,7 @@ class MyUpdateView(MyViewMixin, UpdateView): # Mixin to protect pages class LoggedInMixin(object): + """ Mixin to force a valid login for protected pages. """ @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super(LoggedInMixin, self).dispatch(*args, **kwargs) @@ -94,6 +98,7 @@ class IndexView(ProtectedTemplateView): template_name = 'index.html' page_title = 'Index' + class ProjectView(ProtectedFormView): template_name = 'projects.html' page_title = 'Projects' @@ -108,6 +113,7 @@ class ProjectView(ProtectedFormView): form.save() return super(ProjectView, self).form_valid(form) + class ProjectUpdateView(ProtectedFormView, SingleObjectMixin): template_name = 'project/update.html' page_title = 'Update project' @@ -172,7 +178,7 @@ class ProjectUpdateView(ProtectedFormView, SingleObjectMixin): class ProjectPersonView(ProtectedFormView, SingleObjectMixin): template_name = 'project/view-person.html' - page_title = 'Person View' + page_title = 'Person List View' form_class = FileUploadForm success_url = '/projects/' @@ -183,6 +189,7 @@ class ProjectPersonView(ProtectedFormView, SingleObjectMixin): from service import Mapper pm = Mapper.PersonMapper() kwargs[u'unmapped_persons'] = pm.get_unmapped_identities(project) + kwargs[u'mapped_persons'] = pm.get_mapped_identities(project) return super(ProjectPersonView, self).get_context_data(**kwargs) def get(self, request, *args, **kwargs): @@ -204,7 +211,7 @@ class ProjectMapPersonView(ProtectedFormView, SingleObjectMixin): page_title = 'Person Mapping' form_class = PersonMapForm - success_url = '/projects/' + success_url = '/project' def get_context_data(self, **kwargs): project = self.object @@ -223,9 +230,21 @@ class ProjectMapPersonView(ProtectedFormView, SingleObjectMixin): def post(self, request, *args, **kwargs): self.project_id = kwargs[u'project_id'] self.person_uuid = kwargs[u'person_uuid'] + self.object = Project.objects.get(pk=self.project_id) # self.success_url = self.success_url + self.project_id + '/update' return super(ProjectMapPersonView, self).post(request, *args, **kwargs) def form_valid(self, form, *args, **kwargs): + from service import Mapper + pm = Mapper.PersonMapper() + person = pm.get_person_by_uuid(self.object, self.person_uuid) + + print str(form.cleaned_data) + # TODO: do mapping according to parameters, override success_url to point to next person! + if form.cleaned_data['action'] == 'new': + pm.create_new_identity(self.object, person) + elif form.cleaned_data['action'] == 'skip': + pass + return super(ProjectMapPersonView, self).form_valid(form) diff --git a/service/Citavi.py b/service/Citavi.py index 19ce0f8..de71a66 100644 --- a/service/Citavi.py +++ b/service/Citavi.py @@ -105,71 +105,11 @@ class ProjectManager(): self._projects[project_id].open() def get_person_by_uuid(self, project_id, uuid): + """ Returns the person matching the given uuid. """ self._add_project(project_id) return self._projects[project_id].get_person_by_uuid(uuid) def get_persons_from_project(self, project_id): + """ Returns all person from a projects. """ self._add_project(project_id) return self._projects[project_id].get_persons() - -""" -def import_sqlite(project_id, sqlite_file): - print "This is import_sqlite speaking!" - print project_id - print sqlite_file - print "Importing data..." - - # Initialize sqlite part - sqlite_engine = create_engine('sqlite+pysqlite:///project_import_9.ctt4', echo=True) - sqlite_session = Session(sqlite_engine) - sqlite_meta = MetaData(bind=sqlite_engine) - sqlite_meta.reflect() - - # Initialize postresql part - psql_engine = create_engine("postgresql://citavi_mapper:foobar2000@localhost:5432/citavi_mapper") - psql_session = Session(psql_engine) - psql_engine.execute(CreateSchema('alchemytest')) # TODO: Catch Exception/Warning/Whatever here - psql_meta = MetaData(bind=psql_engine, schema='alchemytest') - - # Reflect the origin - maybe psql_meta.reflect() is enough? - sqlite_autobase = automap_base() - sqlite_autobase.prepare(sqlite_engine, reflect=True) - - - # This small snippet prints python class code for sqlalchemy reflected classes - for table in sqlite_meta.sorted_tables: - table_name = str(table) - columns = table.columns.items() - print "class " + table_name + "(Base):" - print " __tablename__ = '" + table_name + "'" - print - for column_tuple in columns: - column_name = column_tuple[0] - actual_sqlalchemy_column = column_tuple[1] - column_type = str(vars(actual_sqlalchemy_column)['type']) - column_type = str(vars(actual_sqlalchemy_column)['type']) - #print table_name + "." + column_name + ': ' + column_type - print " " + column_name + " = Column(" + column_type + ")" - print - print - - # Shove it up into postgresql's ... you know. - psql_meta.create_all(psql_engine) - - #sqlite_meta.reflect() - - #meta.tables['Reference'] - #meta.tables['ReferenceAuthor'].select().execute().fetchall() - - # Now we need to "convert" unsupported types :-/ - for table in meta.sorted_tables: - table_name = str(table) - columns = table.columns.items() - for column_tuple in columns: - column_name = column_tuple[0] - actual_sqlalchemy_column = column_tuple[1] - column_type = str(vars(actual_sqlalchemy_column)['type']) - print table_name + "." + column_name + ': ' + column_type - print - print -""" diff --git a/service/Django.py b/service/Django.py index 88c1b47..37e2623 100644 --- a/service/Django.py +++ b/service/Django.py @@ -4,8 +4,9 @@ from frontend.models import PersonGlobalIdentity, CitaviProjectIdentity class PersonIdentityManager(): - + """ Class wrapping the django model layer to manage identities. """ def create_identity(self, project, uuid): + """ Creates a new identity and connects it to the given citavi project uuid. """ pgi = PersonGlobalIdentity(type='citavi') pgi.save() cpi = CitaviProjectIdentity(global_identity=pgi, project=project, citavi_uuid=uuid, preferred=True) @@ -13,12 +14,15 @@ class PersonIdentityManager(): return cpi def add_identity_to_global_identity(self, global_identity, project, uuid, preferred): + """ Maps given citavi project identity to existing global identity. """ cpi = CitaviProjectIdentity(global_identity=global_identity, project=project, citavi_uuid=uuid, preferred=True) cpi.save() return cpi def get_global_identities(self): + """ Returns all global identities. """ return PersonGlobalIdentity.objects.all() def get_mapped_identities_for_project(self, project_instance): + """ Returns all existing (mapped) identies for a given project. """ return CitaviProjectIdentity.objects.filter(project=project_instance).all() diff --git a/service/Mapper.py b/service/Mapper.py index 47e7c2b..2b78610 100644 --- a/service/Mapper.py +++ b/service/Mapper.py @@ -3,53 +3,46 @@ from Citavi import ProjectManager from Django import PersonIdentityManager + class PersonMapper(): def __init__(self): + """ Initializes ProjectManager and PersonIdentityManager. """ self._citavi_project_manager = ProjectManager() self._person_identity_manager = PersonIdentityManager() def __del__(self): + """ Destroys ProjectManager and PersonIdentityManager. """ del self._citavi_project_manager del self._person_identity_manager def get_unmapped_identities(self, project): + """ Returns a uuid->person dict for all unmapped persons within a project. """ citavi_persons = self._citavi_project_manager.get_persons_from_project(project.id) mapped_persons = self._person_identity_manager.get_mapped_identities_for_project(project) - - print "Len citavi persons: " + str(len(citavi_persons)) - print "Len mapped persons: " + str(len(mapped_persons)) - # Prepare citavi_persons into a uuid->person dict, then eliminate mapped ones. This is ugly and a little slow. citavi_uuid_dict = {} for person in citavi_persons: citavi_uuid_dict[person.ID] = person - if len(mapped_persons) == 0: return citavi_uuid_dict - - print len(citavi_uuid_dict) - for person in mapped_persons: if person.citavi_uuid in citavi_uuid_dict: -# print "Match for: " + str(person.id) + " - uuid: " + str(person.citavi_uuid) del citavi_uuid_dict[person.citavi_uuid] - - print len(citavi_uuid_dict) - return citavi_uuid_dict + def get_mapped_identities(self, project): + """ Returns a uuid->person dict for all mapped persons within a project. """ + mapped_persons = self._person_identity_manager.get_mapped_identities_for_project(project) + mapped_uuid_dict = {} + for person in mapped_persons: + mapped_uuid_dict[person.citavi_uuid] = person + return mapped_uuid_dict + def create_new_identity(self, project, person): - self._person_identity_manager.create_identity(project, person.ID) + """ Creates a new identity for a given person. """ + return self._person_identity_manager.create_identity(project, person.ID) - def get_person_by_uuid(self, project, uuid): # + def get_person_by_uuid(self, project, uuid): + """ Returns a person from a citavi project by uuid. """ return self._citavi_project_manager.get_person_by_uuid(project.id, uuid) - -def test(): - from frontend.models import Project - project = Project.objects.get(id=1) - p = PersonMapper() - unmapped = p.get_unmapped_identities(project) - print unmapped - for person in unmapped: - p.create_new_identity(project, person)
    IDPrefixTitleFirst NameMiddle NameLast NameSuffixSexAbv.Citavi_UUIDFeatures Features
    {{person.ID}}{{person.Prefix}}{{person.Title}}{{person.FirstName}}{{person.MiddleName}}{{person.LastName}}{{person.Suffix}}{{person.Sex}}{{person.Abbreviation}}{{person.citavi_uuid}}{{person.global_identity}} - Map + Delete Mapping
    {{project.associated_filename}} Update project - Persons + Persons