def team_ips(self): """A list of IPs per team.""" form = ButtonForm(self.request.POST, csrf_context=self.request) redir = self.redirect("admin_teams", int(self.request.GET.get("page", 1))) if not form.validate(): return redir team = DBSession.query(Team).filter(Team.id == form.id.data).options(subqueryload("team_ips")).one() return {"team": team}
def team_resend_activation(self): """Resend the activation mail for a team.""" form = ButtonForm(self.request.POST, csrf_context=self.request) redir = self.redirect("admin_teams", int(self.request.GET.get("page", 1))) if not form.validate(): return redir team = DBSession.query(Team).filter(Team.id == form.id.data).one() send_activation_mail(team, self.request) self.request.session.flash('Activation mail for team "%s" resent.' % team.name) return redir
def team_regenerate_token(self): """Manually regenerate the teams challenge token""" current_page = int(self.request.GET.get("page", 1)) redirect = self.redirect("admin_teams", current_page) button_form = ButtonForm(self.request.POST, csrf_context=self.request) if not button_form.validate(): self.request.session.flash("Regenerate failed.") return redirect team = DBSession.query(Team).filter(Team.id == button_form.id.data).one() log.info("Generating new token for team %s, old token: %s" % (team.name, team.challenge_token)) team.challenge_token = str(uuid.uuid4()).decode("ascii") self.request.session.flash("New token created for team %s" % team.name) return redirect
def _admin_delete(self, route_name, DatabaseClass, title, title_plural=None): """ Generic function to delete a single item from the database. Its arguments have the same meaning as explained in :meth:`_admin_list` with the addition of ``title_plural`` which is just a pluraized version of the ``title`` argument. Also returns something that can be returned directly to the application. .. note:: To avoid problems with cascade instead of just emitting an SQL ``DELETE`` statement, this queries for all affected objects (should be one) and deletes them afterwards. This ensures that the Python-side cascades appropriately delete all dependent objects. """ # We don't accept GET or others here assert self.request.method == "POST" # Prepare parameters delete_form = ButtonForm(self.request.POST, csrf_context=self.request) current_page = int(self.request.GET.get("page", 1)) redirect = self.redirect(route_name, current_page) if title_plural is None: title_plural = title + "s" # Check for errors if not delete_form.validate(): self.request.session.flash("Delete failed.") return redirect # Load the ID to delete item_id = delete_form.id.data # Delete the item item = DBSession.query(DatabaseClass).filter(DatabaseClass.id == item_id) self._delete_item(DBSession, item, title, title_plural) return redirect
def _admin_toggle_status( self, route_name, DatabaseClass, title="", status_types={False: False, True: True}, status_variable_name="published", status_messages={False: "Unpublished %(title)s", True: "Published %(title)s"}, ): """ Generic function that allows to toggle a special status on the challenge. By default it toggles the ``published`` property of any given item. Many arguments are the same as in :meth:`_admin_list` with these additional arguments: ``status_types``: A two-element dictionary that contains ``True`` and ``False`` as keys and any value that describes the given status. For example: If the "unpublished" status is described by the string "offline", then the value for key ``False`` would be ``"offline"``. It depends on the database model, which value is used here. The default is just a boolean mapping. ``status_variable_name``: What is the name of the property in the model that contains the status to be changed. Defaults to "published". ``status_messages``: The same keys as for ``status_types`` but as values contains messages to be displayed, based on which action was the result. Gives access to the ``title`` variable via ``%(title)s`` inside the string. The defaults are sensible values for the the default status. Most likely you want to change this if changing ``status_variable_name``. Returns: A dictionary or similar that can be directly returned from a view. """ # We don't accept GET or others here assert self.request.method == "POST" # Prepare parameters current_page = int(self.request.GET.get("page", 1)) redirect = self.redirect(route_name, current_page) # Load and check form (csrf check!) toggle_form = ButtonForm(self.request.POST, csrf_context=self.request) if not toggle_form.validate(): self.request.session.flash("Toggle failed.") return redirect # Generate a dict of inverted status types inverse_status_types = dict((value, key) for key, value in status_types.items()) # Fetch the item to toggle item = DBSession.query(DatabaseClass).filter(DatabaseClass.id == toggle_form.id.data).one() # Read the current status status = inverse_status_types[getattr(item, status_variable_name)] # Set the inverse setattr(item, status_variable_name, status_types[not status]) # Finish the request self.request.session.flash(status_messages[not status] % {"title": title.lower()}) return redirect
def _admin_list(self, route_name, FormClass, DatabaseClass, title, change_query=None): """ A generic function for all views that contain a list of things and also a form to edit or add entries. .. note:: This only handles items with their own single primary key and not anything with composite foreign keys. Args: ``route_name``: A string containing the name of the route to which the admin should be redirected aver an edit was saved. For example ``"admin_challenges"``. ``FormClass``: The class of the form that should be displayed at the bottom of the page to edit or add items. For example :class:`fluxscoreboard.forms.admin.ChallengeForm`. ``DatabaseClass``: The ORM class from the model that is used to add and fetch items. For example :class:`fluxscoreboard.models.challenge.Challenge`. ``title``: A string that expresses a singular item, for example ``"Challenge"``. Will be used for flash messages. ``change_query``: A function that receives one parameter (a query), modifies it and returns the new query. May for example be used to modify the order or refine results. Optional. Returns: A dictionary or similar that can be directly returned to the application to be rendered as a view. An example usage might be like this: .. code-block:: python def challenges(self): return self._admin_list('admin_challenges', ChallengeForm, Challenge, "Challenge") """ # Prepare some paramters items = self.items(DatabaseClass) if change_query: items = change_query(items) page = self.page(items) redirect = self.redirect(route_name, page.page) item_id = None if self.request.method == "POST": # Someone wants to edit so make sure to load and check the form # for CSRF and get the ID. edit_form = ButtonForm(self.request.POST, csrf_context=self.request) if not edit_form.validate(): # Error, most likely CSRF return redirect item_id = edit_form.id.data # Either load a new form or load old data into it. if item_id is None: form = FormClass(self.request.POST, csrf_context=self.request) else: db_item = DBSession.query(DatabaseClass).filter(DatabaseClass.id == item_id).one() form = FormClass(None, db_item, csrf_context=self.request) # Display the page return self._list_retparams(page, form)