[TASK] Cleanup, begin implementing a mapping wizard for citavi persons.

This commit is contained in:
Jan Philipp Timme 2014-09-29 13:40:07 +02:00
parent b0a04b951e
commit 9b06e3aac6
9 changed files with 69 additions and 115 deletions

View File

@ -8,7 +8,8 @@ admin.autodiscover()
from frontend.views import login_wrap, logout_wrap from frontend.views import login_wrap, logout_wrap
from frontend.views import IndexView, ProjectView, ProjectUpdateView, ProjectPersonView, ProjectMapPersonView from frontend.views import IndexView, ProjectView, ProjectUpdateView, ProjectPersonView, ProjectMapPersonView
urlpatterns = patterns('', urlpatterns = patterns(
'',
url(r'^$', IndexView.as_view(), name='frontend-index'), url(r'^$', IndexView.as_view(), name='frontend-index'),
url(r'^project/$', ProjectView.as_view(), name='frontend-projects'), url(r'^project/$', ProjectView.as_view(), name='frontend-projects'),
url(r'^project/(?P<project_id>\d+)/update$', ProjectUpdateView.as_view(), name='frontend-project-update'), url(r'^project/(?P<project_id>\d+)/update$', ProjectUpdateView.as_view(), name='frontend-project-update'),

View File

@ -61,9 +61,10 @@ class PersonMapForm(forms.Form):
self.helper.layout = Layout( self.helper.layout = Layout(
'action', 'action',
'global_identity', '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'), 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()) 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()) global_identity = forms.ModelChoiceField(queryset=PersonGlobalIdentity.objects.all(), required=False)

View File

@ -4,6 +4,7 @@ from django.db import models
class Project(models.Model): class Project(models.Model):
""" Model representing a citavi project. """
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField() description = models.TextField()
associated_filename = models.CharField(max_length=255, blank=True, null=True) associated_filename = models.CharField(max_length=255, blank=True, null=True)
@ -18,6 +19,7 @@ class Project(models.Model):
class PersonGlobalIdentity(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) type = models.CharField(max_length=255)
# TODO: Extend this for further stuff - maybe vivo external url or something? # TODO: Extend this for further stuff - maybe vivo external url or something?
@ -27,6 +29,7 @@ class PersonGlobalIdentity(models.Model):
class CitaviProjectIdentity(models.Model): class CitaviProjectIdentity(models.Model):
""" Model representing an identity from a citavi project. """
global_identity = models.ForeignKey(PersonGlobalIdentity, blank=True, null=True) global_identity = models.ForeignKey(PersonGlobalIdentity, blank=True, null=True)
project = models.ForeignKey(Project, blank=False, null=False) project = models.ForeignKey(Project, blank=False, null=False)
citavi_uuid = models.CharField(max_length=255, blank=False, null=False) citavi_uuid = models.CharField(max_length=255, blank=False, null=False)

View File

@ -6,6 +6,7 @@
<li><a href="{% url 'frontend-projects' %}">Back to projects</a></li> <li><a href="{% url 'frontend-projects' %}">Back to projects</a></li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h3>Project</h3>
<p><strong>Name:</strong> {{project.name}}</h3> <p><strong>Name:</strong> {{project.name}}</h3>
<p><strong>Description:</strong> {{project.description}}</h3> <p><strong>Description:</strong> {{project.description}}</h3>
<p><strong>Current uploaded database filename:</strong> {{project.associated_filename}}</p> <p><strong>Current uploaded database filename:</strong> {{project.associated_filename}}</p>
@ -15,7 +16,13 @@
<p>{% crispy form %}</p> <p>{% crispy form %}</p>
--> -->
<h3>List of unmapped citavi persons in project:</h3> <h3>Stats</h3>
<p>
<strong>Mapped:</strong> {{mapped_persons|length}}<br>
<strong>Unmapped:</strong> {{unmapped_persons|length}}<br>
</p>
<h3>List of unmapped citavi persons in project</h3>
{% if not unmapped_persons %} {% if not unmapped_persons %}
<p><strong>No unmapped persons in this project. Congratulations!</strong></p> <p><strong>No unmapped persons in this project. Congratulations!</strong></p>
{% else %} {% else %}
@ -55,39 +62,25 @@
</table> </table>
{% endif %} {% endif %}
<hr> <hr>
<h3>List of mapped citavi persons in project:</h3> <h3>List of mapped citavi persons in project</h3>
{% if not mapped_persons %} {% if not mapped_persons %}
<p><strong>No mapped persons in this project. Work harder!</strong></p> <p><strong>No mapped persons in this project. Work harder!</strong></p>
{% else %} {% else %}
<table class="table table-hover table-bordered"> <table class="table table-hover table-bordered">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th>Citavi_UUID</th>
<th>Prefix</th> <th>Features</th>
<th>Title</th>
<th>First Name</th>
<th>Middle Name</th>
<th>Last Name</th>
<th>Suffix</th>
<th>Sex</th>
<th>Abv.</th>
<th>Features</th> <th>Features</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for person in mapped_persons.items %} {% for person_id, person in mapped_persons.items %}
<tr> <tr>
<td>{{person.ID}}</td> <td>{{person.citavi_uuid}}</td>
<td>{{person.Prefix}}</td> <td>{{person.global_identity}}</td>
<td>{{person.Title}}</td>
<td>{{person.FirstName}}</td>
<td>{{person.MiddleName}}</td>
<td>{{person.LastName}}</td>
<td>{{person.Suffix}}</td>
<td>{{person.Sex}}</td>
<td>{{person.Abbreviation}}</td>
<td> <td>
<a href="{% url 'frontend-project-map-person' project.id person.ID %}" title="Enter mapping wizard starting here.">Map</a> <a href="#TODO" title="Enter mapping wizard starting here.">Delete Mapping</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -28,7 +28,7 @@
<td>{{project.associated_filename}}</td> <td>{{project.associated_filename}}</td>
<td> <td>
<a href="{% url 'frontend-project-update' project.id %}">Update project</a> <a href="{% url 'frontend-project-update' project.id %}">Update project</a>
<a href="{% url 'frontend-project-map-person' project.id %}">Persons</a> <a href="{% url 'frontend-project-view-person' project.id %}">Persons</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -21,6 +21,7 @@ FRONTEND_PAGE_NAME = 'Citavi Mapper'
# Login wrapper functions # Login wrapper functions
def login_wrap(*args, **kwargs): def login_wrap(*args, **kwargs):
""" Wrapper function for login page. """
kwargs['extra_context'] = { kwargs['extra_context'] = {
'page': { 'page': {
'name': FRONTEND_PAGE_NAME, 'name': FRONTEND_PAGE_NAME,
@ -30,6 +31,7 @@ def login_wrap(*args, **kwargs):
return login(*args, **kwargs) return login(*args, **kwargs)
def logout_wrap(*args, **kwargs): def logout_wrap(*args, **kwargs):
""" Wrapper function for logout page. """
kwargs['extra_context'] = { kwargs['extra_context'] = {
'page': { 'page': {
'name': FRONTEND_PAGE_NAME, 'name': FRONTEND_PAGE_NAME,
@ -41,6 +43,7 @@ def logout_wrap(*args, **kwargs):
# My base classes for views # My base classes for views
class MyViewMixin(object): class MyViewMixin(object):
""" Basic view mixin to add global variables to all templates. """
template_name = 'base.html' template_name = 'base.html'
page_name = FRONTEND_PAGE_NAME page_name = FRONTEND_PAGE_NAME
page_title = 'BASE' page_title = 'BASE'
@ -70,6 +73,7 @@ class MyUpdateView(MyViewMixin, UpdateView):
# Mixin to protect pages # Mixin to protect pages
class LoggedInMixin(object): class LoggedInMixin(object):
""" Mixin to force a valid login for protected pages. """
@method_decorator(login_required) @method_decorator(login_required)
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):
return super(LoggedInMixin, self).dispatch(*args, **kwargs) return super(LoggedInMixin, self).dispatch(*args, **kwargs)
@ -94,6 +98,7 @@ class IndexView(ProtectedTemplateView):
template_name = 'index.html' template_name = 'index.html'
page_title = 'Index' page_title = 'Index'
class ProjectView(ProtectedFormView): class ProjectView(ProtectedFormView):
template_name = 'projects.html' template_name = 'projects.html'
page_title = 'Projects' page_title = 'Projects'
@ -108,6 +113,7 @@ class ProjectView(ProtectedFormView):
form.save() form.save()
return super(ProjectView, self).form_valid(form) return super(ProjectView, self).form_valid(form)
class ProjectUpdateView(ProtectedFormView, SingleObjectMixin): class ProjectUpdateView(ProtectedFormView, SingleObjectMixin):
template_name = 'project/update.html' template_name = 'project/update.html'
page_title = 'Update project' page_title = 'Update project'
@ -172,7 +178,7 @@ class ProjectUpdateView(ProtectedFormView, SingleObjectMixin):
class ProjectPersonView(ProtectedFormView, SingleObjectMixin): class ProjectPersonView(ProtectedFormView, SingleObjectMixin):
template_name = 'project/view-person.html' template_name = 'project/view-person.html'
page_title = 'Person View' page_title = 'Person List View'
form_class = FileUploadForm form_class = FileUploadForm
success_url = '/projects/' success_url = '/projects/'
@ -183,6 +189,7 @@ class ProjectPersonView(ProtectedFormView, SingleObjectMixin):
from service import Mapper from service import Mapper
pm = Mapper.PersonMapper() pm = Mapper.PersonMapper()
kwargs[u'unmapped_persons'] = pm.get_unmapped_identities(project) 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) return super(ProjectPersonView, self).get_context_data(**kwargs)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -204,7 +211,7 @@ class ProjectMapPersonView(ProtectedFormView, SingleObjectMixin):
page_title = 'Person Mapping' page_title = 'Person Mapping'
form_class = PersonMapForm form_class = PersonMapForm
success_url = '/projects/' success_url = '/project'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
project = self.object project = self.object
@ -223,9 +230,21 @@ class ProjectMapPersonView(ProtectedFormView, SingleObjectMixin):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.project_id = kwargs[u'project_id'] self.project_id = kwargs[u'project_id']
self.person_uuid = kwargs[u'person_uuid'] 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' # self.success_url = self.success_url + self.project_id + '/update'
return super(ProjectMapPersonView, self).post(request, *args, **kwargs) return super(ProjectMapPersonView, self).post(request, *args, **kwargs)
def form_valid(self, form, *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) return super(ProjectMapPersonView, self).form_valid(form)

View File

@ -105,71 +105,11 @@ class ProjectManager():
self._projects[project_id].open() self._projects[project_id].open()
def get_person_by_uuid(self, project_id, uuid): def get_person_by_uuid(self, project_id, uuid):
""" Returns the person matching the given uuid. """
self._add_project(project_id) self._add_project(project_id)
return self._projects[project_id].get_person_by_uuid(uuid) return self._projects[project_id].get_person_by_uuid(uuid)
def get_persons_from_project(self, project_id): def get_persons_from_project(self, project_id):
""" Returns all person from a projects. """
self._add_project(project_id) self._add_project(project_id)
return self._projects[project_id].get_persons() 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
"""

View File

@ -4,8 +4,9 @@ from frontend.models import PersonGlobalIdentity, CitaviProjectIdentity
class PersonIdentityManager(): class PersonIdentityManager():
""" Class wrapping the django model layer to manage identities. """
def create_identity(self, project, uuid): def create_identity(self, project, uuid):
""" Creates a new identity and connects it to the given citavi project uuid. """
pgi = PersonGlobalIdentity(type='citavi') pgi = PersonGlobalIdentity(type='citavi')
pgi.save() pgi.save()
cpi = CitaviProjectIdentity(global_identity=pgi, project=project, citavi_uuid=uuid, preferred=True) cpi = CitaviProjectIdentity(global_identity=pgi, project=project, citavi_uuid=uuid, preferred=True)
@ -13,12 +14,15 @@ class PersonIdentityManager():
return cpi return cpi
def add_identity_to_global_identity(self, global_identity, project, uuid, preferred): 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 = CitaviProjectIdentity(global_identity=global_identity, project=project, citavi_uuid=uuid, preferred=True)
cpi.save() cpi.save()
return cpi return cpi
def get_global_identities(self): def get_global_identities(self):
""" Returns all global identities. """
return PersonGlobalIdentity.objects.all() return PersonGlobalIdentity.objects.all()
def get_mapped_identities_for_project(self, project_instance): 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() return CitaviProjectIdentity.objects.filter(project=project_instance).all()

View File

@ -3,53 +3,46 @@
from Citavi import ProjectManager from Citavi import ProjectManager
from Django import PersonIdentityManager from Django import PersonIdentityManager
class PersonMapper(): class PersonMapper():
def __init__(self): def __init__(self):
""" Initializes ProjectManager and PersonIdentityManager. """
self._citavi_project_manager = ProjectManager() self._citavi_project_manager = ProjectManager()
self._person_identity_manager = PersonIdentityManager() self._person_identity_manager = PersonIdentityManager()
def __del__(self): def __del__(self):
""" Destroys ProjectManager and PersonIdentityManager. """
del self._citavi_project_manager del self._citavi_project_manager
del self._person_identity_manager del self._person_identity_manager
def get_unmapped_identities(self, project): 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) citavi_persons = self._citavi_project_manager.get_persons_from_project(project.id)
mapped_persons = self._person_identity_manager.get_mapped_identities_for_project(project) 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. # Prepare citavi_persons into a uuid->person dict, then eliminate mapped ones. This is ugly and a little slow.
citavi_uuid_dict = {} citavi_uuid_dict = {}
for person in citavi_persons: for person in citavi_persons:
citavi_uuid_dict[person.ID] = person citavi_uuid_dict[person.ID] = person
if len(mapped_persons) == 0: if len(mapped_persons) == 0:
return citavi_uuid_dict return citavi_uuid_dict
print len(citavi_uuid_dict)
for person in mapped_persons: for person in mapped_persons:
if person.citavi_uuid in citavi_uuid_dict: 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] del citavi_uuid_dict[person.citavi_uuid]
print len(citavi_uuid_dict)
return 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): 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) 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)