import logging
from django import forms
from django.db import models
from django.http import Http404, HttpResponseRedirect
from django.contrib import messages
from django.core.urlresolvers import reverse, reverse_lazy
from django.db import IntegrityError, transaction
from django.utils.safestring import mark_safe
from django.core.exceptions import NON_FIELD_ERRORS, ImproperlyConfigured
from vanilla import (
ListView, UpdateView, CreateView, DeleteView,
TemplateView, DetailView, FormView, RedirectView)
from braces.views import FormInvalidMessageMixin, SetHeadlineMixin
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, HTML
from crispy_forms.bootstrap import FormActions
from . import forward
from .forms import tripsyear_modelform_factory
from .models import TripsYear
from .urlhelpers import reverse_update_url, reverse_delete_url
from fyt.permissions.views import (
DatabaseReadPermissionRequired, DatabaseEditPermissionRequired)
from fyt.utils.views import CrispyFormMixin, SetExplanationMixin, ExtraContextMixin
logger = logging.getLogger(__name__)
FORM_INVALID_MESSAGE = "Uh oh! Looks like there's an error in the form"
[docs]class TripsYearMixin():
"""
Mixin for ``trips_year``.
Filters objects by the ``trips_year`` named group in the url.
Plugs into ModelViews. The url is a database url of the form
/something/{{trips_year}}/something. The ListView will only display
objects for the specified trips_year.
"""
[docs] def dispatch(self, request, *args, **kwargs):
"""
Make sure the request is for a valid trips year.
Requesting a ``trips_year`` that don't exist in the db will
cause problems. Block 'em here.
"""
trips_year = self.get_trips_year()
if not TripsYear.objects.filter(year=trips_year).exists():
msg = 'Trips %s does not exist in the database'
raise Http404(msg % trips_year)
return super(TripsYearMixin, self).dispatch(request, *args, **kwargs)
[docs] def get_trips_year(self):
"""
Pull trips_year out of url kwargs.
"""
return self.kwargs['trips_year']
[docs] def get_queryset(self):
"""
Filter objects for the trips_year of the request.
"""
qs = super(TripsYearMixin, self).get_queryset()
return qs.filter(trips_year=self.get_trips_year())
[docs] def get_context_data(self, **kwargs):
"""
Add the trips_year for this request to the context.
"""
context = super(TripsYearMixin, self).get_context_data(**kwargs)
context['trips_year'] = self.get_trips_year()
return context
class DatabaseListView(DatabaseReadPermissionRequired, ExtraContextMixin,
TripsYearMixin, ListView):
pass
class DatabaseCreateView(DatabaseEditPermissionRequired, ExtraContextMixin,
FormInvalidMessageMixin,
SetExplanationMixin, SetHeadlineMixin,
TripsYearMixin, CrispyFormMixin, CreateView):
fields = '__all__'
template_name = 'db/create.html'
form_invalid_message = FORM_INVALID_MESSAGE
def get_headline(self):
return "Add a new %s" % self.model._meta.verbose_name.title()
def post(self, request, *args, **kwargs):
"""
Add trips_year to created object.
This is the vanilla CreateView, verbatim, with the addition
of the trips_year.
"""
form = self.get_form(data=request.POST, files=request.FILES)
form.instance.trips_year_id = self.get_trips_year()
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
def get_form_helper(self, form):
""" Add 'Create' button to crispy form. """
helper = FormHelper(form)
helper.add_input(Submit('submit', 'Create'))
return helper
def get_success_url(self):
""" TODO: for now... """
from fyt.db.urlhelpers import reverse_detail_url
return reverse_detail_url(self.object)
[docs]class DatabaseUpdateView(DatabaseEditPermissionRequired, ExtraContextMixin,
SetHeadlineMixin, FormInvalidMessageMixin, TripsYearMixin,
CrispyFormMixin, UpdateView):
"""
Base view for updating an object in the database.
If 'delete_button' is True, a link to the delete view for
this object will be added to the form's button holder.
"""
template_name ='db/update.html'
form_invalid_message = FORM_INVALID_MESSAGE
delete_button = True # add a "Delete" button?
fields = '__all__'
def get_headline(self):
return mark_safe("Edit %s <small> %s </small>" % (
self.model._meta.verbose_name.title(), self.object)
)
def get_success_url(self):
from fyt.db.urlhelpers import reverse_detail_url
return reverse_detail_url(self.object)
class DatabaseDeleteView(DatabaseEditPermissionRequired, ExtraContextMixin,
SetHeadlineMixin,TripsYearMixin, DeleteView):
template_name = 'db/delete.html'
success_url_pattern = None
def get_headline(self):
return "Are you sure you want to delete %s %s?" % (
self.object.get_model_name(), self.object
)
def get_success_url(self):
"""
Helper method for getting the success url based on the
succes_url_pattern property.
CreateView and UpdateView use the models get_absolute_url
to find the success_url. DeleteView cannot do this because the
target object hsa been deleted.
"""
if self.success_url_pattern:
kwargs = {'trips_year': self.get_trips_year()}
return reverse(self.success_url_pattern, kwargs=kwargs)
return super(DatabaseDeleteView, self).get_success_url()
def post(self, request, *args, **kwargs):
"""
Warn when a foreign key is protected and the object
cannot be deleted.
"""
try:
resp = super(DatabaseDeleteView, self).post(request, *args, **kwargs)
messages.success(
request, "Succesfully deleted {}".format(self.object)
)
return resp
except models.ProtectedError as e:
msg = (
"Oops, you can't delete {} {} because the "
"following objects reference it: {}."
).format(
self.object._meta.model.__name__,
self.object, e.protected_objects
)
messages.error(request, msg)
return HttpResponseRedirect(request.path)
class DatabaseDetailView(DatabaseReadPermissionRequired, ExtraContextMixin,
TripsYearMixin, DetailView):
template_name = 'db/detail.html'
# Fields to display in the view. Passed in the template.
fields = None
def get_context_data(self, **kwargs):
kwargs['update_url'] = reverse_update_url(self.object)
kwargs['delete_url'] = reverse_delete_url(self.object)
return super(DatabaseDetailView, self).get_context_data(**kwargs)
class DatabaseTemplateView(DatabaseReadPermissionRequired, ExtraContextMixin,
TripsYearMixin, TemplateView):
pass
[docs]class DatabaseLandingPage(DatabaseTemplateView):
"""
Landing page of a particular trips_year in the database
TODO: should this display the Trips index?
"""
template_name = 'db/landing_page.html'
[docs]class RedirectToCurrentDatabase(DatabaseReadPermissionRequired, RedirectView):
"""
Redirect to the trips database for the current year.
This view is the target of database urls.
"""
permanent = False
def get_redirect_url(self, *args, **kwargs):
trips_year = TripsYear.objects.current()
return reverse('db:landing_page', kwargs={'trips_year': trips_year.pk})
[docs]class MigrateForward(DatabaseEditPermissionRequired, SetHeadlineMixin,
ExtraContextMixin, TripsYearMixin, FormView):
"""
Migrate the database to the next ``trips_year`
"""
template_name = 'db/form.html'
success_url = reverse_lazy('db:db_redirect')
headline = "Migrate"
def get_trips_year(self):
return TripsYear.objects.current().year
def get_form(self, **kwargs):
form = forms.Form(**kwargs)
form.helper = FormHelper()
form.helper.add_input(Submit('submit', 'Migrate'))
return form
def form_valid(self, form):
forward.forward()
return super(MigrateForward, self).form_valid(form)