123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- from django import forms
- from django.core.exceptions import ValidationError
- from django.core.urlresolvers import reverse
- from django.shortcuts import redirect
- from django.template import RequestContext
- from django.utils.translation import ugettext_lazy as _
- from jinja2 import TemplateNotFound
- import math
- from misago.forms import Form, FormLayout, FormFields, FormFieldsets
- from misago.messages import Message
- """
- Class widgets
- """
- class BaseWidget(object):
- """
- Admin Widget abstract class, providing widgets with common or shared functionality
- """
- admin = None
- id = None
- fallback = None
- name = None
- help = None
- notfound_message = None
- def __new__(cls, request, **kwargs):
- obj = super(BaseWidget, cls).__new__(cls)
- if not obj.name:
- obj.name = obj.get_name()
- if not obj.help:
- obj.help = obj.get_help()
- return obj(request, **kwargs)
- def get_token(self, token):
- return '%s_%s_%s' % (self.id, token, str('%s.%s' % (self.admin.id, self.admin.model.__name__)))
- def get_url(self):
- return reverse(self.admin.get_action_attr(self.id, 'route'))
- def get_name(self):
- return self.admin.get_action_attr(self.id, 'name')
- def get_help(self):
- return self.admin.get_action_attr(self.id, 'help')
- def get_id(self):
- return 'admin_%s' % self.id
- def get_template(self):
- return ('%s/%s.html' % (self.admin.id, self.template),
- 'admin/%s.html' % self.template)
- def add_template_variables(self, variables):
- return variables
- def get_fallback_url(self):
- return reverse(self.fallback)
- def get_target(self, model):
- pass
- def get_target_name(self, model):
- try:
- if self.translate_target_name:
- return _(model.__dict__[self.target_name])
- return model.__dict__[self.target_name]
- except AttributeError:
- return None
- def get_and_validate_target(self, target):
- try:
- model = self.admin.model.objects.select_related().get(pk=target)
- self.get_target(model)
- return model
- except self.admin.model.DoesNotExist:
- self.request.messages.set_flash(Message(self.notfound_message), 'error', self.admin.id)
- except ValueError as e:
- self.request.messages.set_flash(Message(e.args[0]), 'error', self.admin.id)
- return None
- class ListWidget(BaseWidget):
- """
- Items list widget
- """
- actions = []
- columns = []
- sortables = {}
- default_sorting = None
- search_form = None
- is_filtering = False
- pagination = None
- template = 'list'
- hide_actions = False
- table_form_button = _('Go')
- empty_message = _('There are no items to display')
- empty_search_message = _('Search has returned no items')
- nothing_checked_message = _('You have to select at least one item.')
- prompt_select = False
- def get_item_actions(self, item):
- """
- Provides request and item, should return list of tuples with item actions in following format:
- (id, name, help, icon, link)
- """
- return []
- def action(self, icon=None, name=None, url=None, post=False, prompt=None):
- """
- Function call to make hash with item actions
- """
- if prompt:
- self.prompt_select = True
- return {
- 'icon': icon,
- 'name': name,
- 'url': url,
- 'post': post,
- 'prompt': prompt,
- }
- def get_search_form(self):
- """
- Build a form object with items search
- """
- return self.search_form
- def set_filters(self, model, filters):
- """
- Set filters on model using filters from session
- """
- return None
- def get_table_form(self, page_items):
- """
- Build a form object with list of all items fields
- """
- return None
- def table_action(self, page_items, cleaned_data):
- """
- Handle table form submission, return tuple containing message and redirect link/false
- """
- return None
- def get_actions_form(self, page_items):
- """
- Build a form object with list of all items actions
- """
- if not self.actions:
- return None # Dont build form
- form_fields = {}
- list_choices = []
- for action in self.actions:
- list_choices.append((action[0], action[1]))
- form_fields['list_action'] = forms.ChoiceField(choices=list_choices)
- list_choices = []
- for item in page_items:
- list_choices.append((item.pk, None))
- form_fields['list_items'] = forms.MultipleChoiceField(choices=list_choices, widget=forms.CheckboxSelectMultiple)
- return type('AdminListForm', (Form,), form_fields)
- def get_sorting(self):
- """
- Return list sorting method.
- A list with three values:
- - Field we use to sort over
- - Sorting direction
- - order_by() argument
- """
- sorting_method = None
- if self.request.session.get(self.get_token('sort')) and self.request.session.get(self.get_token('sort'))[0] in self.sortables:
- sorting_method = self.request.session.get(self.get_token('sort'))
- if self.request.GET.get('sort') and self.request.GET.get('sort') in self.sortables:
- new_sorting = self.request.GET.get('sort')
- sorting_dir = int(self.request.GET.get('dir')) == 1
- sorting_method = [
- new_sorting,
- sorting_dir,
- new_sorting if sorting_dir else '-%s' % new_sorting
- ]
- self.request.session[self.get_token('sort')] = sorting_method
- if not sorting_method:
- if self.sortables:
- new_sorting = self.sortables.keys()[0]
- if self.default_sorting in self.sortables:
- new_sorting = self.default_sorting
- sorting_method = [
- new_sorting,
- self.sortables[new_sorting] == True,
- new_sorting if self.sortables[new_sorting] else '-%s' % new_sorting
- ]
- else:
- sorting_method = [
- id,
- True,
- '-id'
- ]
- return sorting_method
- def sort_items(self, page_items, sorting_method):
- return page_items.order_by(sorting_method[2])
- def get_pagination_url(self, page):
- return reverse(self.admin.get_action_attr(self.id, 'route'), kwargs={'page': page})
- def get_pagination(self, total, page):
- """
- Return list pagination.
- A list with three values:
- - Offset for ORM slicing
- - Length of slice
- - no. of prev page (or -1 for first page)
- - no. of next page (or -1 for last page)
- - Current page
- - Pages total
- """
- if not self.pagination or total < 0:
- # Dont do anything if we are not paging
- return None
- # Set basic pagination, use either Session cache or new page value
- pagination = {'start': 0, 'stop': 0, 'prev':-1, 'next':-1}
- if self.request.session.get(self.get_token('pagination')):
- pagination['start'] = self.request.session.get(self.get_token('pagination'))
- page = int(page)
- if page > 0:
- pagination['start'] = (page - 1) * self.pagination
- # Set page and total stat
- pagination['page'] = int(pagination['start'] / self.pagination) + 1
- pagination['total'] = int(math.ceil(total / float(self.pagination)))
- # Fix too large offset
- if pagination['start'] > total:
- pagination['start'] = 0
- # Allow prev/next?
- if total > self.pagination:
- if pagination['page'] > 1:
- pagination['prev'] = pagination['page'] - 1
- if pagination['page'] < pagination['total']:
- pagination['next'] = pagination['page'] + 1
- # Set stop offset
- pagination['stop'] = pagination['start'] + self.pagination
- return pagination
- def get_items(self):
- if self.request.session.get(self.get_token('filter')):
- self.is_filtering = True
- return self.set_filters(self.admin.model.objects, self.request.session.get(self.get_token('filter')))
- return self.admin.model.objects
- def __call__(self, request, page=0):
- """
- Use widget as view
- """
- self.request = request
- # Get basic list items
- items_total = self.get_items()
- # Set extra filters?
- try:
- items_total = self.select_items(items_total).count()
- except AttributeError:
- items_total = items_total.count()
- # Set sorting and paginating
- sorting_method = self.get_sorting()
- paginating_method = self.get_pagination(items_total, page)
- # List items
- items = self.get_items()
- if not request.session.get(self.get_token('filter')):
- items = items.all()
- # Set extra filters?
- try:
- items = self.select_items(items)
- except AttributeError:
- pass
- # Sort them
- items = self.sort_items(items, sorting_method);
- # Set pagination
- if self.pagination:
- items = items[paginating_method['start']:paginating_method['stop']]
- # Prefetch related?
- try:
- items = self.prefetch_related(items)
- except AttributeError:
- pass
- # Default message
- message = None
- # See if we should make and handle search form
- search_form = None
- SearchForm = self.get_search_form()
- if SearchForm:
- if request.method == 'POST':
- # New search
- if request.POST.get('origin') == 'search':
- search_form = SearchForm(request.POST, request=request)
- if search_form.is_valid():
- search_criteria = {}
- for field, criteria in search_form.cleaned_data.items():
- if len(criteria) > 0:
- search_criteria[field] = criteria
- if not search_criteria:
- message = Message(_("No search criteria have been defined."))
- else:
- request.session[self.get_token('filter')] = search_criteria
- return redirect(self.get_url())
- else:
- message = Message(_("Search form contains errors."))
- message.type = 'error'
- else:
- search_form = SearchForm(request=request)
- # Kill search
- if request.POST.get('origin') == 'clear' and self.is_filtering and request.csrf.request_secure(request):
- request.session[self.get_token('filter')] = None
- request.messages.set_flash(Message(_("Search criteria have been cleared.")), 'info', self.admin.id)
- return redirect(self.get_url())
- else:
- if self.is_filtering:
- search_form = SearchForm(request=request, initial=request.session.get(self.get_token('filter')))
- else:
- search_form = SearchForm(request=request)
- # See if we sould make and handle tab form
- table_form = None
- TableForm = self.get_table_form(items)
- if TableForm:
- if request.method == 'POST' and request.POST.get('origin') == 'table':
- table_form = TableForm(request.POST, request=request)
- if table_form.is_valid():
- message, redirect_url = self.table_action(items, table_form.cleaned_data)
- if redirect_url:
- request.messages.set_flash(message, message.type, self.admin.id)
- return redirect(redirect_url)
- else:
- message = Message(table_form.non_field_errors()[0], 'error')
- else:
- table_form = TableForm(request=request)
- # See if we should make and handle list form
- list_form = None
- ListForm = self.get_actions_form(items)
- if ListForm:
- if request.method == 'POST' and request.POST.get('origin') == 'list':
- list_form = ListForm(request.POST, request=request)
- if list_form.is_valid():
- try:
- form_action = getattr(self, 'action_' + list_form.cleaned_data['list_action'])
- message, redirect_url = form_action(items, list_form.cleaned_data['list_items'])
- if redirect_url:
- request.messages.set_flash(message, message.type, self.admin.id)
- return redirect(redirect_url)
- except AttributeError:
- message = Message(_("Action requested is incorrect."))
- else:
- if 'list_items' in list_form.errors:
- message = Message(self.nothing_checked_message)
- elif 'list_action' in list_form.errors:
- message = Message(_("Action requested is incorrect."))
- else:
- message = Message(list_form.non_field_errors()[0])
- message.type = 'error'
- else:
- list_form = ListForm(request=request)
- # Little hax to keep counters correct
- items_shown = len(items)
- if items_total < items_shown:
- items_total = items_shown
- # Render list
- return request.theme.render_to_response(self.get_template(),
- self.add_template_variables({
- 'admin': self.admin,
- 'action': self,
- 'request': request,
- 'url': self.get_url(),
- 'messages_log': request.messages.get_messages(self.admin.id),
- 'message': message,
- 'sorting': self.sortables,
- 'sorting_method': sorting_method,
- 'pagination': paginating_method,
- 'list_form': FormLayout(list_form) if list_form else None,
- 'search_form': FormLayout(search_form) if search_form else None,
- 'table_form': FormFields(table_form).fields if table_form else None,
- 'items': items,
- 'items_total': items_total,
- 'items_shown': items_shown,
- }),
- context_instance=RequestContext(request));
- class FormWidget(BaseWidget):
- """
- Form page widget
- """
- template = 'form'
- submit_button = _("Save Changes")
- form = None
- layout = None
- tabbed = False
- target_name = None
- translate_target_name = False
- original_name = None
- submit_fallback = False
- def get_url(self, model):
- return reverse(self.admin.get_action_attr(self.id, 'route'))
- def get_form(self, target):
- return self.form
- def get_form_instance(self, form, target, initial, post=False):
- if post:
- return form(self.request.POST, request=self.request, initial=self.get_initial_data(target))
- return form(request=self.request, initial=self.get_initial_data(target))
- def get_layout(self, form, model):
- if self.layout:
- return self.layout
- return form.layout
- def get_initial_data(self, model):
- return {}
- def submit_form(self, form, model):
- """
- Handle form submission, ALWAYS return tuple with model and message
- """
- pass
- def __call__(self, request, target=None, slug=None):
- self.request = request
- # Fetch target?
- model = None
- if target:
- model = self.get_and_validate_target(target)
- self.original_name = self.get_target_name(model)
- if not model:
- return redirect(self.get_fallback_url())
- original_model = model
- # Get form type to instantiate
- FormType = self.get_form(model)
- #Submit form
- message = None
- if request.method == 'POST':
- form = self.get_form_instance(FormType, model, self.get_initial_data(model), True)
- if form.is_valid():
- try:
- model, message = self.submit_form(form, model)
- if message.type != 'error':
- request.messages.set_flash(message, message.type, self.admin.id)
- # Redirect back to right page
- try:
- if 'save_new' in request.POST and self.get_new_url:
- return redirect(self.get_new_url(model))
- except AttributeError:
- pass
- try:
- if 'save_edit' in request.POST and self.get_edit_url:
- return redirect(self.get_edit_url(model))
- except AttributeError:
- pass
- try:
- if self.get_submit_url:
- return redirect(self.get_submit_url(model))
- except AttributeError:
- pass
- return redirect(self.get_fallback_url())
- except ValidationError as e:
- message = Message(e.messages[0], 'error')
- else:
- message = Message(form.non_field_errors()[0], 'error')
- else:
- form = self.get_form_instance(FormType, model, self.get_initial_data(model))
- # Render form
- return request.theme.render_to_response(self.get_template(),
- self.add_template_variables({
- 'admin': self.admin,
- 'action': self,
- 'request': request,
- 'url': self.get_url(model),
- 'fallback': self.get_fallback_url(),
- 'messages_log': request.messages.get_messages(self.admin.id),
- 'message': message,
- 'tabbed': self.tabbed,
- 'target': self.get_target_name(original_model),
- 'target_model': original_model,
- 'form': FormLayout(form, self.get_layout(form, target)),
- }),
- context_instance=RequestContext(request));
- class ButtonWidget(BaseWidget):
- """
- Button Action Widget
- This widget handles most basic and common type of admin action - button press:
- - User presses button on list (for example "delete this user!")
- - Widget checks if request is CSRF-valid and POST
- - Widget optionally chcecks if target has been provided and action is allowed at all
- - Widget does action and redirects us back to fallback url
- """
- def __call__(self, request, target=None, slug=None):
- self.request = request
- # Fetch target?
- model = None
- if target:
- model = self.get_and_validate_target(target)
- if not model:
- return redirect(self.get_fallback_url())
- original_model = model
- # Crash if this is invalid request
- if not request.csrf.request_secure(request):
- request.messages.set_flash(Message(_("Action authorization is invalid.")), 'error', self.admin.id)
- return redirect(self.get_fallback_url())
- # Do something
- message, url = self.action(model)
- request.messages.set_flash(message, message.type, self.admin.id)
- if url:
- return redirect(url)
- return redirect(self.get_fallback_url())
- def action(self, target):
- """
- Action to be executed when button is pressed
- Define custom one in your Admin action.
- It should return response and message objects
- """
- pass
|