# -*- coding: utf-8 -*- # File upload related imports import os from django.core.files.storage import default_storage from django.core.files.base import ContentFile from django.conf import settings from django.views.generic import TemplateView, FormView, CreateView, UpdateView from django.views.generic.detail import SingleObjectMixin from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.contrib.auth.views import login, logout from django.core.urlresolvers import reverse from frontend.forms import NewProjectForm, FileUploadForm, PersonMapForm from frontend.forms import PresentationForm, PresentationSubmissionForm from frontend.forms import PresentationEventFormHelper, PresentationPersonFormHelper from frontend.models import Presentation, PresentationEvent, PresentationPerson from frontend.formsets import PresentationEventInlineFormset, PresentationPersonInlineFormset from frontend.models import Project FRONTEND_PAGE_NAME = u'Citavi Mapper' from service.Mapper import person_mapper # Login wrapper functions def login_wrap(*args, **kwargs): """ Wrapper function for login page. """ kwargs[u'extra_context'] = { u'page': { u'name': FRONTEND_PAGE_NAME, u'title': 'Login' } } return login(*args, **kwargs) def logout_wrap(*args, **kwargs): """ Wrapper function for logout page. """ kwargs[u'extra_context'] = { u'page': { u'name': FRONTEND_PAGE_NAME, u'title': 'Logout' } } return logout(*args, **kwargs) # My base classes for views class MyViewMixin(object): """ Basic view mixin to add global variables to all templates. """ template_name = u'base.html' page_name = FRONTEND_PAGE_NAME page_title = u'BASE' def get_page_data(self): return { u'name': self.page_name, u'title': self.page_title } def get_context_data(self, **kwargs): kwargs[u'page'] = self.get_page_data() return super(MyViewMixin, self).get_context_data(**kwargs) class MyTemplateView(MyViewMixin, TemplateView): pass class MyFormView(MyViewMixin, FormView): pass class MyCreateView(MyViewMixin, CreateView): pass class MyUpdateView(MyViewMixin, UpdateView): pass # 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) # My protected view classes class ProtectedTemplateView(LoggedInMixin, MyTemplateView): pass class ProtectedFormView(LoggedInMixin, MyFormView): pass class ProtectedFormSetView(LoggedInMixin, MyTemplateView): pass class ProtectedCreateView(LoggedInMixin, MyCreateView): pass class ProtectedUpdateView(LoggedInMixin, MyUpdateView): pass # Actual Views class IndexView(ProtectedTemplateView): template_name = u'index.html' page_title = u'Index' class PresentationAddView(ProtectedTemplateView): template_name = u'presentation-add.html' page_title = u'Vortrag anlegen' success_url = u'presentation-add' """ These guys are passed through using get_context_data(). """ presentation_form = None submission_form = None person_formset = None event_formset = None person_formhelper = PresentationPersonFormHelper event_formhelper = PresentationEventFormHelper success = False error = False def __init__(self, *args, **kwargs): super(PresentationAddView, self).__init__(*args, **kwargs) def get(self, request, *args, **kwargs): """ Create empty form + formsets by default. """ self.create_blank_forms() return super(PresentationAddView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): """ Just use the same template and go on by default. Also, trigger handling the forms. """ self.handle_forms(request, *args, **kwargs) context = self.get_context_data(**kwargs) return self.render_to_response(context) def create_blank_forms(self): """ Create blank form + formsets for the user to fill. """ self.presentation_form = PresentationForm(prefix='presentation') self.submission_form = PresentationSubmissionForm(prefix='submission') self.person_formset = PresentationPersonInlineFormset(prefix='person') self.event_formset = PresentationEventInlineFormset(prefix='event') def get_context_data(self, **kwargs): """ Add form + formsets to template context. """ kwargs[u'presentation_form'] = self.presentation_form kwargs[u'submission_form'] = self.submission_form kwargs[u'person_formset'] = self.person_formset kwargs[u'event_formset'] = self.event_formset kwargs[u'person_formhelper'] = self.person_formhelper kwargs[u'event_formhelper'] = self.event_formhelper kwargs[u'success'] = self.success kwargs[u'error'] = self.error return super(PresentationAddView, self).get_context_data(**kwargs) def handle_forms(self, request, *args, **kwargs): """ Once a POST came in, try and validate the forms, saving the data. """ presentation = None presentation_ok = False person_ok = False event_ok = False submission_ok = False """ Create all the forms and formsets based on request params to do validation. """ self.presentation_form = PresentationForm(request.POST, request.FILES, prefix='presentation') self.person_formset = PresentationPersonInlineFormset(request.POST, request.FILES, prefix='person') self.event_formset = PresentationEventInlineFormset(request.POST, request.FILES, prefix='event') self.submission_form = PresentationSubmissionForm(request.POST, request.FILES, prefix='submission') """ Check all for validity """ presentation_ok = self.presentation_form.is_valid() submission_ok = self.submission_form.is_valid() person_ok = self.person_formset.is_valid() """ Only other event type needs a valid event formset. """ print(presentation_ok, person_ok, event_ok, submission_ok) if presentation_ok and self.presentation_form.cleaned_data[u'type'] == 'other': event_ok = self.event_formset.is_valid() else: event_ok = True """ Only if everything is alright, store it.""" if presentation_ok == True and person_ok == True and event_ok == True and submission_ok == True: presentation = self.presentation_form.save() """ Now we can create the other ones based on the saved presentation to save them, too. """ submission_form = PresentationSubmissionForm(request.POST, request.FILES, instance=presentation, prefix='submission') submission_form.save() person_formset = PresentationPersonInlineFormset(request.POST, request.FILES, instance=presentation, prefix='person') person_formset.save() """ Conditionally save the events depending on whether they are needed. """ if presentation.type == 'other': event_formset = PresentationEventInlineFormset(request.POST, request.FILES, instance=presentation, prefix='event') event_formset.save() """ we're done. """ self.success = True self.create_blank_forms() else: self.error = True class ProjectView(ProtectedFormView): template_name = u'projects.html' page_title = u'Projects' form_class = NewProjectForm success_url = u'/project' def get_context_data(self, **kwargs): kwargs[u'projects'] = Project.objects.order_by(u'id') return super(ProjectView, self).get_context_data(**kwargs) def form_valid(self, form): form.save() return super(ProjectView, self).form_valid(form) class ProjectUpdateView(ProtectedFormView, SingleObjectMixin): template_name = u'project/update.html' page_title = u'Update project' form_class = FileUploadForm success_url = u'/project/' def get(self, request, *args, **kwargs): self.project_id = kwargs[u'project_id'] self.object = Project.objects.get(pk=self.project_id) return super(ProjectUpdateView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.project_id = kwargs[u'project_id'] self.object = Project.objects.get(pk=self.project_id) self.success_url = self.success_url + self.project_id + u'/update' return super(ProjectUpdateView, self).post(request, *args, **kwargs) def form_valid(self, form, *args, **kwargs): """ This form_valid handles the file upload. """ original_file = form.files[u'file'] original_filename = unicode(original_file) original_contentfile = ContentFile(original_file.read()) """ Put file into temporary folder for analysis """ target_filename = u'tmp/project_' + unicode(self.project_id) + u'.ctt4' relative_path = default_storage.save(target_filename, original_contentfile) temp_sqlite = os.path.join(settings.MEDIA_ROOT, relative_path) """ Test if SQLite is a valid citavi project. """ from service import Citavi citavi_project = Citavi.Project(temp_sqlite) citavi_project.open() citavi_project_valid = citavi_project.is_valid() """ Free temporary ressources. """ del citavi_project default_storage.delete(temp_sqlite) if citavi_project_valid == False: """ TODO: Put up an error message or something. """ pass else: target_filename = u'citavi/project_' + unicode(self.project_id) + u'.ctt4' """ Remove eventually pre-existing citavi file. """ if default_storage.exists(target_filename): default_storage.delete(target_filename) """ Actually store file in citavi folder """ relative_path = default_storage.save(target_filename, original_contentfile) sqlite_path = os.path.join(settings.MEDIA_ROOT, relative_path) """ Store new original filename in Project """ project = Project.objects.get(id=self.project_id) project.associated_filename = original_filename project.save() """ Refresh identities from citavi project. """ # TODO # citavi_project = Citavi.Project(sqlite_path) # citavi_project.open() # del citavi_project return super(ProjectUpdateView, self).form_valid(form) class ProjectPersonView(ProtectedFormView, SingleObjectMixin): template_name = u'project/view-person.html' page_title = u'Person List View' form_class = FileUploadForm success_url = u'/project/' def get_context_data(self, **kwargs): project = self.object kwargs[u'unmapped_persons'] = person_mapper.get_unmapped_identities(project) kwargs[u'mapped_persons'] = person_mapper.get_mapped_identities(project) return super(ProjectPersonView, self).get_context_data(**kwargs) def get(self, request, *args, **kwargs): self.project_id = kwargs[u'project_id'] self.object = Project.objects.get(pk=self.project_id) return super(ProjectPersonView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.project_id = kwargs[u'project_id'] # self.success_url = self.success_url + self.project_id + '/update' return super(ProjectPersonView, self).post(request, *args, **kwargs) def form_valid(self, form, *args, **kwargs): return super(ProjectPersonView, self).form_valid(form) class ProjectMapPersonView(ProtectedFormView, SingleObjectMixin): """ View containing the person mapping 'wizard'. """ template_name = u'project/map-person.html' page_title = u'Person Mapping' form_class = PersonMapForm success_url = u'/project' _unmapped_persons = None def _refresh_unmapped(self): """ Refresh data about unmapped persons. """ self._unmapped_persons = person_mapper.get_unmapped_identities(self.object) def _is_unmapped(self, person_uuid): """ Returns True if the given person is not mapped yet. """ if not self._unmapped_persons: self._refresh_unmapped() return person_uuid in self._unmapped_persons def get_context_data(self, **kwargs): project = self.object kwargs[u'person'] = person_mapper.get_person_by_uuid(project, self.person_uuid) return super(ProjectMapPersonView, self).get_context_data(**kwargs) def get_success_url(self): """ TODO: Make this work! - Update success uri to next unmapped person. """ last_uuid = unicode(self.person_uuid) self._refresh_unmapped() unmapped = self._unmapped_persons.items() first_person_uuid = unmapped[0][0] next_uuid = first_person_uuid unmapped.reverse() while True: try: current_person = unmapped.pop() if current_person[1].ID == last_uuid: next_uuid = unmapped.pop()[0] break except IndexError: break kwargs = { u"project_id": self.object.id, u"person_uuid": next_uuid } return reverse('frontend-project-map-person', kwargs=kwargs) def get(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) if self._is_unmapped(self.person_uuid) == False: raise Exception("Sorry, this person was already mapped. Try deleting the existing mapping and move on. TODO: Make this more beautiful.") self._refresh_unmapped() return super(ProjectMapPersonView, self).get(request, *args, **kwargs) 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) if self._is_unmapped(self.person_uuid) == False: raise Exception("Sorry, this person was already mapped. Try deleting the existing mapping and move on. TODO: Make this more beautiful.") self._refresh_unmapped() return super(ProjectMapPersonView, self).post(request, *args, **kwargs) def form_valid(self, form, *args, **kwargs): person = person_mapper.get_person_by_uuid(self.object, self.person_uuid) if form.data.get(u'skip', False): """ Nothing to do here, just go on ... """ pass elif form.data.get(u'save-continue', False): # TODO: do mapping according to parameters, override success_url to point to next person! if form.cleaned_data[u'action'] == u'new': person_mapper.create_new_identity(self.object, person) elif form.cleaned_data[u'action'] == u'existing': global_identity = form.cleaned_data[u'global_identity'] person_mapper.map_identity_to_existing(global_identity, self.object, person.ID, form.cleaned_data[u'preferred_identity']) return super(ProjectMapPersonView, self).form_valid(form)