# -*- coding: utf-8 -*- from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.utils.encoding import force_text from rapid.registry import registry, Action from rapid import filters import itertools from django.db import models from rapid import permissions class InstanceData(object): def __init__(self, instance, request=None, excludes=None, creator=None, fields=None): excludes = [] if excludes is None else excludes self.model = ModelData(type(instance), request, excludes, creator, fields) self.instance = instance self.request = request self.excludes = excludes if excludes else [] self.creator = creator self._fields = self.model.fields() def values(self): r = [] o = self.instance for f in self.model.fields(): if f.is_relation: r.append(self._value_of_field(o, f)) else: r.append(self._value_of_field(o, f)) return r def _value_of_field(self, instance, field): """ Returns the value of the given field. ::return A tuple, with the actual value of the field, a boolean that is true iff the value is iterable, and a sequence of URLs for the viewers of the instances of the first element, or False if there are no viewers. """ if hasattr(instance, field.accessor_name()): v = getattr(instance, field.accessor_name()) else: # Many to many relations without value may disappear return [], True if hasattr(v, '__iter__'): return (v, ()), True if hasattr(v, 'all'): return [(x, InstanceData(x, self.request, creator=(self, field))) for x in v.all()], True if isinstance(v, models.Model): return (v, InstanceData(v, self.request, creator=(self, field))), False return (v, ()), False def fields_and_values(self): r = [] for field in self.model.fields(): value, is_multiple = self._value_of_field(self.instance, field) r.append((field, value, is_multiple)) return r def is_controlled(self): return self.model.is_controlled() def can_read(self): return self.has_permission(self.request, 'view') def can_write(self): return self.has_permission(self.request, 'edit') def view_url(self): return registry.get_url_of_action(self.model.model, "view", pk=self.instance.pk) def edit_url(self): url = registry.get_url_of_action(self.model.model, "edit", pk=self.instance.pk) by = self.creator if by: dt, fd = by if fd.one_to_one or fd.one_to_many: # Do not change the parent of the viewer. return url + "?default=" + fd.field.name + ":" + str(dt.object.pk) if fd.many_to_one or fd.many_to_many: return url return url def remove_url(self): return registry.get_url_of_action(self.model.model, "delete", pk=self.instance.pk) def create_url(self): return registry.get_url_of_action(self.model.model, "add") def list_url(self): return registry.get_url_of_action(self.model.model, "list") def select_url(self): return registry.get_url_of_action(self.model.model, "select") def actions(self): r = [] acts = registry.model_entry(self.model.model) if self.request and acts: for a in acts.values(): if self.has_permission(self.request, a.action.name) and\ a.action.visibility > Action.Visibility.hidden: r.append((a, a.get_url(self.instance))) return r def model_actions(self): r = [] for (a, u) in self.actions(): if not a.action.query_parameters: r.append((a, u)) return r def instance_actions(self): r = [] for (a, u) in self.actions(): if a.action.query_parameters: r.append((a, u)) return r def list_actions(self): r = [] for (a, u) in self.instance_actions(): if a.action.visibility == Action.Visibility.list: r.append((a, u)) return r def has_permission(self, request, action_name): m = registry.model_entry(self.model.model).get(action_name) if m: perm = m.permission.instances return permissions.has_instance(self.model, perm(request), self.instance) return False def __unicode__(self): return force_text(self.instance) def __str__(self): return str(self.model) + ': ' + str(self.instance.pk) class ModelData(object): def __init__(self, model, request=None, excludes=None, creator=None, fields=None): excludes = [] if excludes is None else excludes self.model = model self.request = request self.excludes = excludes if excludes else [] self.creator = creator self._fields = [self.field_by_name(f) for f in fields] if fields else self.all_fields() def model_name(self): # noinspection PyProtectedMember return force_text(self.model._meta.verbose_name) def model_name_plural(self): # noinspection PyProtectedMember return force_text(self.model._meta.verbose_name_plural) def default_manager(self): # noinspection PyProtectedMember return self.model._default_manager def all_fields(self): r = [] relations = [] ignore_fields = [] for f in itertools.chain(self.local_fields(), self.related_fields()): if f.bare_name() in ignore_fields: ignore_fields.remove(f.bare_name()) continue if hasattr(f.field, 'is_rapid_alternatives') and f.field.is_rapid_alternatives: for ff in self.rapid_alternative_data(): if ff[1].ct_field == f.bare_name(): f = FieldData(ff[1]) for fr in r: if fr.bare_name() == ff[1].fk_field: r.remove(fr) ignore_fields.append(ff[1].fk_field) break if f.is_relation(): relations.append(f) else: if f.name not in self.excludes: r.append(f) for f in relations: if f.name not in self.excludes: r.append(f) return r def fields(self): return self._fields def local_fields(self): """ :return: FieldData for each local (not related) field of this model. """ r = [] # noinspection PyProtectedMember for f in self.model._meta.local_fields: if f.name not in self.excludes: r.append(FieldData(f, self.request)) # noinspection PyProtectedMember for f in self.model._meta.local_many_to_many: if f.name not in self.excludes: r.append(FieldData(f, self.request)) return r def rapid_alternative_data(self): """ :return: Tuples of the form (name, field) with the AlternativeData fields of this model. Those are not returned by fields() """ for k, v in self.model.__dict__.items(): if hasattr(v, 'is_rapid_alternatives') and v.is_rapid_alternatives: yield (k, v) def related_fields(self): """ :return: A FieldData for each related manager for this model. """ # noinspection PyProtectedMember return [FieldData(f, self.request) for f in self.model._meta.get_all_related_objects()] def is_controlled(self): """ :return: True if this model is already controlled by the rapid registry. """ return registry.is_controlled(self.model) def can_read(self): if self.can_write(): return True vw = registry.model_entry(self.model)['view'].permission(self.request) if vw: return vw.exists() return False def can_write(self): ed = registry.model_entry(self.model)['edit'].permission(self.request) if ed: return ed.exists() return False def create_url(self): """ :return: URL of the "add" action. """ return registry.get_url_of_action(self.model, "add") def list_url(self): """ :return: URL of the "list" action. """ return registry.get_url_of_action(self.model, "list") def select_url(self): """ :return: URL of the "select" action. """ return registry.get_url_of_action(self.model, "select") def actions(self): """ :return: All actions available for this model (with the object's request). """ r = [] acts = registry.model_entry(self.model) if self.request and acts: for a in acts.values(): if self.has_permission(self.request, a.action.name) and\ not a.action.query_parameters and\ a.action.visibility > Action.Visibility.hidden: r.append((a, a.get_url())) return r def has_permission(self, request, action_name): m = registry.model_entry(self.model) if m: a = m.get(action_name) if a: return bool(a.permission.model(request)) return False def field_by_name(self, field_name): """ :return: A FieldData with the field of this model that has the given name. """ # noinspection PyProtectedMember return FieldData(self.model._meta.get_field(field_name), self.request) def content_type(self): """ :return: The Django registry ContentType for this model. """ return ContentType.objects.get_for_model(self.model) def __unicode__(self): return force_text(self.model) def __str__(self): return 'Model: ' + str(self.model) class FieldData(object): def __init__(self, field, request=None): self.field = field self.request = request @classmethod def from_model(cls, model, field_name): ff = ModelData(model).fields() for f in ff: if f.bare_name() == force_text(field_name): return f return None def bare_name(self): return force_text(self.field.name) def accessor_name(self): if hasattr(self.field, 'get_accessor_name'): return force_text(self.field.get_accessor_name()) return force_text(self.field.name) def name(self): if self.is_auto() and self.is_relation(): return self.related_model().model_name_plural() + ' - ' + self.related_field().name() if hasattr(self.field, "verbose_name"): return force_text(self.field.verbose_name) return force_text(self.field.name) def name_plural(self): if self.is_auto() and self.is_relation(): return self.related_model().model_name_plural() + ' - ' + self.related_field().name_plural() if hasattr(self.field, "verbose_name_plural"): return force_text(self.field.verbose_name_plural) return self.name() + "s" def is_relation(self): return self.field.is_relation def is_multiple(self): if not self.is_relation(): return False if self.field.one_to_many: return True if self.field.many_to_many: return True return False def related_model(self): if hasattr(self.field, "related_model"): return ModelData(self.field.related_model) if hasattr(self.field, "to"): return ModelData(self.field.to) return None def related_field(self): if hasattr(self.field, "field"): return FieldData(self.field.field, self.request) return None def is_auto(self): return self.field.auto_created def is_existential_dependency(self): """ :return: True if this field determine an existential dependency relation between this model and another one. That is, returns true if, because of this field, one element of this model can only exist if there is one element of one other model. Example: A required ForeignKey. """ if not self.is_relation(): return False f = self.field if hasattr(f, "many_to_many") and f.many_to_many: return False if hasattr(f, "many_to_one") and self.field.many_to_one: return False if hasattr(self.field, "required"): return self.field.required return True def filter_html(self): return filters.Filter.selection_type_html(self, self.request) def __str__(self): return self.bare_name() class ValueData(object): def __init__(self, value, field): self.value = value self.field = field def can_view(self): if self.field.is_relation(): o = self.field.related_model() return registry.is_controlled(o) return False def is_multiple(self): return self.field.is_multiple() def __str__(self): return str(self.field) + ': ' + str(self.value)