Browse Source

Creating alternative data sets.

Marcos Dumay de Medeiros 8 years ago
parent
commit
6fb40c16a2

+ 1 - 0
TODO

@@ -0,0 +1 @@
+Many2many does not appear in list

+ 1 - 1
src/rapid/forms.py

@@ -6,7 +6,7 @@ from django import forms
 
 from rapid.models import Profile, Application
 from rapid.wrappers import FieldData
-from rapid.widgets import RapidReadOnly, RapidRelationReadOnly, RapidSelector
+from rapid.widgets import RapidReadOnly, RapidRelationReadOnly, RapidSelector, rapidAlternativesWidget
 
 class ManageUsers(forms.ModelForm):
     class Meta:

+ 0 - 5
src/rapid/models.py

@@ -3,11 +3,6 @@
 from django.db import models
 from django.contrib.auth.models import User
 
-class CrudModel(models.Model):
-    class Meta:
-        abstract = True
-
-
 class Application(models.Model):
     name = models.CharField(max_length=60, unique=True, verbose_name=u"nome")
     python_name = models.CharField(max_length=255, unique=True, verbose_name=u"Nome no Python")

+ 129 - 126
src/rapid/permissions.py

@@ -1,126 +1,129 @@
-__author__ = 'marcos.medeiros'
-
-
-class Permission:
-    """
-    A permission for an registry entry.
-    """
-    def __init__(self, model, instances):
-        self.model = model
-        self.instances = instances
-
-
-def all_instances(model):
-    """
-    Shortuct function for granting permission over all instances of a model.
-    For that use, create a permission with this function at its "instances" attribute.
-    """
-    def i(request):
-        if model(request):
-            return {}
-        else:
-            return None
-    return i
-
-
-def apply_instances_permission(model, perm):
-    """
-    Returns the set of objects that a resolved permission has access to.
-    :param model: ModelData of the model that'll be filtered
-    :param perm: Resolved permission (that is, the result of evaluating permission.instances(request))
-    """
-    if perm is None:
-        return []
-    if hasattr(perm, 'keys'):
-        return model.default_manager().filter(**perm)
-    if hasattr(perm, '__iter__'):
-        return perm
-    if hasattr(perm, 'all'):
-        return perm
-    return []
-
-
-def has_instance(model, perm, instance):
-    """
-    Verifies if an object instance access is permited
-    :param model: ModelData of the desired model
-    :param perm: Resolved permission (that is, the result of evaluating permission.instances(request))
-    :param instance: Instance that'll be verified.
-    """
-    p = apply_instances_permission(model, perm)
-    if hasattr(p, 'filter'):
-        return p.filter(pk=instance.pk).exists()
-    if hasattr(p, '__iter__'):
-        return bool([f for f in p if f.pk == instance.pk])
-    return False
-
-
-def to_profile(profile):
-    """
-    Grants permission over the model and all instances to the given profile(s)
-    :param profile: A profile.id or an iterable of those.
-    """
-    if hasattr(profile, "__iter__"):
-        def m(request):
-            if not request.user.is_authenticated():
-                return False
-            up = [p.pk for p in request.user.profile_set]
-            for p in up:
-                if p in profile:
-                    return True
-            return False
-    else:
-        def m(request):
-            if not request.user.is_authenticated():
-                return False
-            up = [p.pk for p in request.user.profile_set.all()]
-            if profile in up:
-                return True
-            return False
-    return Permission(m, all_instances(m))
-
-
-def to_staff():
-    """
-    Grants permission over the model and all instances to every user with is_staff set.
-    """
-    def m(request):
-        if request.user.is_authenticated() and request.user.is_staff:
-            return True
-        return False
-    return Permission(m, all_instances(m))
-
-
-def to_all():
-    """
-    Grants permission over the model and all instances to all users.
-    """
-    def m(request):
-        return True
-    return Permission(m, all_instances(m))
-
-
-def to_superusers():
-    """
-    Grants permission over the model and all instances to every user with superuser set.
-    """
-    def m(request):
-        if request.user.is_authenticated() and request.user.is_superuser:
-            return True
-        return False
-    return Permission(m, all_instances(m))
-
-
-def to_application_managers(app):
-    """
-    Grants permission over the model and all instances to the manager of the given application
-    :param app: Application.id of the desired application
-    """
-    def m(request):
-        if not request.user.is_authenticated():
-            return False
-        up = [a.pk for a in request.user.managed_applications.all()]
-        if app in up:
-            return True
-        return False
-    return Permission(m, all_instances(m))
+# coding=utf-8
+
+__author__ = 'marcos.medeiros'
+
+
+class Permission(object):
+    """
+    A permission for an registry entry.
+    """
+    def __init__(self, model, instances):
+        self.model = model
+        self.instances = instances
+
+
+def all_instances(model):
+    """
+    Shortcut function for granting permission over all instances of a model.
+    For that use, create a permission with this function at its "instances" attribute.
+    """
+    def i(request):
+        if model(request):
+            return {}
+        else:
+            return None
+    return i
+
+
+def apply_instances_permission(model, perm):
+    """
+    Returns the set of objects that a resolved permission has access to.
+    :param model: ModelData of the model that'll be filtered
+    :param perm: Resolved permission (that is, the result of evaluating permission.instances(request))
+    """
+    if perm is None:
+        return []
+    if hasattr(perm, 'keys'):
+        return model.default_manager().filter(**perm)
+    if hasattr(perm, '__iter__'):
+        return perm
+    if hasattr(perm, 'all'):
+        return perm
+    return []
+
+
+def has_instance(model, perm, instance):
+    """
+    Verifies if an object instance access is permitted
+    :param model: ModelData of the desired model
+    :param perm: Resolved permission (that is, the result of evaluating permission.instances(request))
+    :param instance: Instance that'll be verified.
+    """
+    p = apply_instances_permission(model, perm)
+    if hasattr(p, 'filter'):
+        return p.filter(pk=instance.pk).exists()
+    if hasattr(p, '__iter__'):
+        return bool([f for f in p if f.pk == instance.pk])
+    return False
+
+
+def to_profile(profile):
+    """
+    Grants permission over the model and all instances to the given profile(s)
+    :param profile: A profile.id or an iterable of those.
+    """
+    if hasattr(profile, "__iter__"):
+        def m(request):
+            if not request.user.is_authenticated():
+                return False
+            up = [p.pk for p in request.user.profile_set]
+            for p in up:
+                if p in profile:
+                    return True
+            return False
+    else:
+        def m(request):
+            if not request.user.is_authenticated():
+                return False
+            up = [p.pk for p in request.user.profile_set.all()]
+            if profile in up:
+                return True
+            return False
+    return Permission(m, all_instances(m))
+
+
+def to_staff():
+    """
+    Grants permission over the model and all instances to every user with is_staff set.
+    """
+    def m(request):
+        if request.user.is_authenticated() and request.user.is_staff:
+            return True
+        return False
+    return Permission(m, all_instances(m))
+
+
+def to_all():
+    """
+    Grants permission over the model and all instances to all users.
+    """
+    # noinspection PyUnusedLocal
+    def m(request):
+        return True
+    return Permission(m, all_instances(m))
+
+
+def to_superusers():
+    """
+    Grants permission over the model and all instances to every user with superuser set.
+    """
+    def m(request):
+        if request.user.is_authenticated() and request.user.is_superuser:
+            return True
+        return False
+    return Permission(m, all_instances(m))
+
+
+def to_application_managers(app):
+    """
+    Grants permission over the model and all instances to the manager of the given application
+    :param app: Application.id of the desired application
+    """
+    def m(request):
+        if not request.user.is_authenticated():
+            return False
+        up = [a.pk for a in request.user.managed_applications.all()]
+        if app in up:
+            return True
+        return False
+    return Permission(m, all_instances(m))

+ 46 - 0
src/rapid/rapidfields.py

@@ -0,0 +1,46 @@
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
+from rapid.rapidforms import RapidAlternativesField
+from django.db import models
+
+__author__ = 'marcos'
+
+"""
+Model fields specific for Rapid Django.
+"""
+
+
+class AlternativeData(GenericForeignKey):
+    """
+    A generic relation that Rapid will display as an inline form or a group of data at the
+     edit and view actions.
+
+    Create like a Django GenericForeignKey, but use an AlternativeDataTables instead of a
+    ForeignKey to ContentTypes.
+    """
+    def formfield(self, **kwargs):
+        kwargs['form_class'] = RapidAlternativesField
+        return super(GenericForeignKey, self).formfield(**kwargs)
+
+
+class AlternativeDataTables(models.ForeignKey):
+    """
+    Use an AlternativeDataTables model field on AlternativeData represetntations, instead of
+    a ForeignKey with ContentTypes.
+    """
+    def __init__(self, alternatives, **kwargs):
+        """
+        Receives the same named parameters of a ForeignKey, but instead of a target table,
+        receives a list of the tables that may keep the alternative data.
+        :param alternatives: List of the tables that hold the alternative data.
+        :param kwargs: Arguments to the ForeignKey, without a relationship target.
+        """
+        pks=[]
+        try:
+            pks = [ContentType.objects.get_for_model(a).pk for a in alternatives]
+        except RuntimeError:
+            #Querying ContentType within a content type is an issue at migrations
+            pass
+        kwargs['limit_choices_to'] = {'pk__in': pks}
+        super(AlternativeDataTables, self).__init__(ContentType, **kwargs)
+        self.alternatives = alternatives

+ 117 - 0
src/rapid/rapidforms.py

@@ -0,0 +1,117 @@
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ValidationError
+from django.db import transaction
+
+__author__ = 'marcos'
+
+import collections
+
+from django import forms
+
+from rapid.widgets import RapidSelector, RapidRelationReadOnly, rapidAlternativesWidget
+from rapid.wrappers import FieldData, ModelData, InstanceData
+
+
+class RapidAlternativesField(forms.Field):
+    def __init__(self, field_name, alternatives, selector_name, form, request, instance=None, *args, **kwargs):
+        model = None
+        if form.base_fields[selector_name].initial:
+            model = ContentType.objects.get(pk=form.base_fields[selector_name].initial)
+        alt = []
+        for a in alternatives:
+            md = ModelData(a)
+            ft = for_model(request, a)
+            prefix = field_name + '_' + str(md.content_type().pk)
+            selected = model == a
+            inst = instance if selected else None
+            if request.method == 'POST':
+                fm = ft(request.POST, request.FILES, prefix=prefix, instance=inst)
+            else:
+                fm = ft(prefix=prefix, instance=inst)
+            alt.append((md.content_type().pk, (md, fm, selected)))
+        alt = dict(alt)
+        kwargs['widget'] = rapidAlternativesWidget(alt, selector_name)
+        # noinspection PyArgumentList
+        super(RapidAlternativesField, self).__init__(field_name, *args, **kwargs)
+
+    def to_python(self, value):
+        if value is None:
+            return None
+        if value.is_valid():
+            obj = value.save(commit=False)
+            obj.save_m2m = value.save_m2m
+            return obj
+        raise ValidationError(_("Invalid value"), code='invalid')
+
+
+def for_model(request, model, default_relations=()):
+    default_relations_request = request.GET.get('default')
+    widgets = []
+    default_relations_fields = []
+    if default_relations_request:
+        default_relations_fields = default_relations_request.split(",")
+        default_relations += ((x, int(y)) for (x, y) in (f.split(":") for f in default_relations_fields))
+        default_relations_fields = [x for x, y in default_relations]
+        for (x, y) in default_relations:
+            f = FieldData(getattr(model, x).field, request)
+            widgets.append((x, RapidRelationReadOnly(f.related_model())))
+    for f in ModelData(model).local_fields():
+        if f.is_relation() and unicode(f.bare_name()) not in default_relations_fields:
+            if f.related_model().has_permission(request, 'select'):
+                widgets.append((f.bare_name(), RapidSelector(f)))
+    # ModelForm.Meta has attributes with the same names, thus I'll rename them
+    form_model = model
+    form_widgets = dict(widgets)
+
+    class CForm(forms.ModelForm):
+        def __init__(self, *args, **kwargs):
+            initial = kwargs.get('initial', {})
+            for (k, v) in default_relations:
+                initial[k] = v
+            if initial:
+                kwargs['initial'] = initial
+            for n, f in ModelData(model).rapid_alternative_data():
+                ct = ModelData(model).field_by_name(f.ct_field).field
+                fk = ModelData(model).field_by_name(f.fk_field).field
+                fl = RapidAlternativesField(n, ct.alternatives, ct.name, self, request, initial)
+                type(self.__class__).__setattr__(self.__class__, n, fl)
+                nd = collections.OrderedDict()
+                for k, v in self.__class__.base_fields.iteritems():
+                    if k == fk.name:
+                        nd[n] = fl
+                    else:
+                        nd[k] = v
+                self.__class__.base_fields = nd
+            super(CForm, self).__init__(*args, **kwargs)
+
+        @transaction.atomic
+        def save(self, commit=True):
+            if not commit:
+                return super(CForm, self).save(commit)
+            else:
+                obj = super(CForm, self).save(commit=False)
+                for n, f in ModelData(model).rapid_alternative_data():
+                    ct = ModelData(model).field_by_name(f.ct_field).field
+                    fk = ModelData(model).field_by_name(f.fk_field).field
+                    if self.instance:
+                        fl = RapidAlternativesField(n, ct.alternatives, ct.name, self, request, self.instance)
+                        old_t = getattr(self.instance, f.ct_field)
+                        new_t = getattr(obj, f.ct_field)
+                        if old_t != new_t:
+                            getattr(self.instance, f.bare_name).delete()
+                    fob = self.cleaned_data[n]
+                    fob.save()
+                    if hasattr(fob, 'save_m2m'):
+                        fob.save_m2m()
+                    setattr(obj, f.fk_field, fob.pk)
+                obj.save()
+                self.save_m2m()
+                return obj
+
+
+        class Meta:
+            model = form_model
+            fields = '__all__'
+            widgets = form_widgets
+
+    return CForm

+ 180 - 175
src/rapid/registry.py

@@ -1,175 +1,180 @@
-from django.core.urlresolvers import reverse
-from rapid.models import Application
-from django.conf.urls import url
-import inspect
-import logging
-from os import path
-
-def _split_all_path(file_name):
-    file_name = path.splitdrive(file_name)[1]
-    p = 'a'
-    while p:
-        file_name, p = path.split(file_name)
-        yield p
-
-def _caller_urls_module():
-    st = inspect.stack()
-    for rec in st:
-        file_name = rec[1]
-        segments = list(_split_all_path(file_name))
-        i = 0
-        for i in xrange(0, len(segments) - 2):
-            p = path.normcase(segments[i])
-            p = path.splitext(p)[0]
-            if p == "urls":
-                return segments[i+1]
-    return None
-
-def _model_name(model):
-    if hasattr(model, "url_name"):
-        return model.url_name
-    return model._meta.verbose_name
-
-class MenuEntry:
-    """
-    The data that goes on a menu item.
-    Model, permissions and url
-    """
-    def __init__(self, model, permission, url_name=None):
-        self.url_name = url_name
-        self.model = model
-        self.permission = permission
-
-    def get_url(self, instance=None):
-        ats = [(x, getattr(instance, x)) for x in self.action.query_parameters]
-        return reverse(self.url, kwargs=dict(ats))
-
-    def __unicode__(self):
-        return u"Menu entry: " + self.url_name + " -> " + unicode(self.action)
-
-    def __str__(self):
-        return str(unicode(self))
-
-
-class ModuleEntry:
-    """
-    Module data used at menu construction
-    """
-    def __init__(self, python_name, menu_name):
-        self.python_name = python_name
-        self.menu_name = menu_name
-        self.models = set()
-
-
-class Action:
-    """
-    An action to be done over a model.
-    Default actions are "list", "view", "edit", "add", "delete", and "select",
-    those are defined at the views module.
-    """
-    def __init__(self, name, url_parameters, query_parameters, view_factory,
-                 verbose_name=None, icon=None, visibility=None):
-        self.name = name
-        self.url_parameters = url_parameters
-        self.query_parameters = query_parameters
-        self.view_factory = view_factory
-        self.verbose_name = verbose_name if verbose_name else name
-        self.icon = icon
-        self.visibility = self.Visibility.details if visibility is None else visibility
-
-    def __unicode__(self):
-        return u"Action: " + self.name
-
-    def __str__(self):
-        return str(unicode(self))
-
-    class Visibility:
-        hidden = 1
-        details = 2
-        list = 3
-
-
-class _Registry:
-    """
-    Registry of URLs, models and views present on the menu
-
-    The registry must:
-        -- for the menu creation
-        list registered modules
-        list registered models
-        list menu entries by module
-        list actions by model
-        reverse url of action and model
-        -- for crud generation
-        query if model is registered
-        list actions by model
-        reverse url of action and model
-    """
-
-    def __init__(self):
-        """
-        Populates the menu registry
-        """
-        self._modules = {}  # ModuleEntry by python_name
-        self._models = {}  # {'action name': MenuEntry} by model class
-        for a in Application.objects.filter(enabled=True):
-            m = ModuleEntry(a.python_name, a.name)
-            self._modules[a.python_name] = m
-
-    def register_action(self, action, entry, **kwargs):
-        """
-        Registers an action at this registry, so it will appear on the menu
-        and can be reversed at the cruds.
-        :param action: Action type
-        :param entry: The menu entry where it will appear
-        :param kwargs: Arguments (besides model) that'll be passed to the view_factory of the action
-        :return: A Django URL pattern that should be added to the patterns of a urls.py module
-        """
-        from django.contrib.auth.models import User
-        module_name = _caller_urls_module()
-        model = entry.model
-        if not module_name:
-            raise Exception("Unidentified python module registering " + str(model))
-        if not registry._modules.has_key(module_name):
-            logging.error("Module " + module_name + " is not set-up for registering cruds")
-            return None
-
-        module_entry = registry._modules[module_name]
-        module_entry.models.add(model)
-
-        if not entry.url_name:
-            entry.url_name = _model_name(model)
-        entry.action = action
-        entry_url = module_entry.menu_name + '_' + entry.url_name + '_' + action.name
-        entry.url = entry_url
-        model_actions = self._models.get(model, {})
-        if model_actions.has_key(action.name):
-            raise Exception("Action " + action.name + " already registered for model " + str(model))
-        model_actions[action.name] = entry
-        self._models[model] = model_actions
-        return url(r'^%s/%s/%s$' % (entry.url_name, action.name, action.url_parameters),
-                   action.view_factory(model=entry.model, **kwargs), name=entry_url)
-
-
-    def get_url_of_action(self, model, action_name, **kwargs):
-        acts = self._models.get(model)
-        if acts and acts.has_key(action_name):
-            return reverse(acts[action_name].url, kwargs=kwargs)
-
-    def modules(self):
-        return self._modules.values()
-
-    def entry_names(self):
-        return [x.menu_name for x in self._modules.values()]
-
-    def is_controlled(self, model):
-        return self._models.has_key(model)
-
-    def model_entry(self, model):
-        return self._models.get(model)
-
-    def module_models(self, module):
-        return self._modules[module].models
-
-
-registry = _Registry()
+from django.core.urlresolvers import reverse
+from django.db.utils import OperationalError
+from rapid.models import Application
+from django.conf.urls import url
+import inspect
+import logging
+from os import path
+
+def _split_all_path(file_name):
+    file_name = path.splitdrive(file_name)[1]
+    p = 'a'
+    while p:
+        file_name, p = path.split(file_name)
+        yield p
+
+def _caller_urls_module():
+    st = inspect.stack()
+    for rec in st:
+        file_name = rec[1]
+        segments = list(_split_all_path(file_name))
+        i = 0
+        for i in xrange(0, len(segments) - 2):
+            p = path.normcase(segments[i])
+            p = path.splitext(p)[0]
+            if p == "urls":
+                return segments[i+1]
+    return None
+
+def _model_name(model):
+    if hasattr(model, "url_name"):
+        return model.url_name
+    return model._meta.verbose_name
+
+class MenuEntry:
+    """
+    The data that goes on a menu item.
+    Model, permissions and url
+    """
+    def __init__(self, model, permission, url_name=None):
+        self.url_name = url_name
+        self.model = model
+        self.permission = permission
+
+    def get_url(self, instance=None):
+        ats = [(x, getattr(instance, x)) for x in self.action.query_parameters]
+        return reverse(self.url, kwargs=dict(ats))
+
+    def __unicode__(self):
+        return u"Menu entry: " + self.url_name + " -> " + unicode(self.action)
+
+    def __str__(self):
+        return str(unicode(self))
+
+
+class ModuleEntry:
+    """
+    Module data used at menu construction
+    """
+    def __init__(self, python_name, menu_name):
+        self.python_name = python_name
+        self.menu_name = menu_name
+        self.models = set()
+
+
+class Action:
+    """
+    An action to be done over a model.
+    Default actions are "list", "view", "edit", "add", "delete", and "select",
+    those are defined at the views module.
+    """
+    def __init__(self, name, url_parameters, query_parameters, view_factory,
+                 verbose_name=None, icon=None, visibility=None):
+        self.name = name
+        self.url_parameters = url_parameters
+        self.query_parameters = query_parameters
+        self.view_factory = view_factory
+        self.verbose_name = verbose_name if verbose_name else name
+        self.icon = icon
+        self.visibility = self.Visibility.details if visibility is None else visibility
+
+    def __unicode__(self):
+        return u"Action: " + self.name
+
+    def __str__(self):
+        return str(unicode(self))
+
+    class Visibility:
+        hidden = 1
+        details = 2
+        list = 3
+
+
+class _Registry:
+    """
+    Registry of URLs, models and views present on the menu
+
+    The registry must:
+        -- for the menu creation
+        list registered modules
+        list registered models
+        list menu entries by module
+        list actions by model
+        reverse url of action and model
+        -- for crud generation
+        query if model is registered
+        list actions by model
+        reverse url of action and model
+    """
+
+    def __init__(self):
+        """
+        Populates the menu registry
+        """
+        self._modules = {}  # ModuleEntry by python_name
+        self._models = {}  # {'action name': MenuEntry} by model class
+        try:
+            for a in Application.objects.filter(enabled=True):
+                m = ModuleEntry(a.python_name, a.name)
+                self._modules[a.python_name] = m
+        except OperationalError:
+            #Should always get fixed by a migration
+            logging.error("Can not query applications table. You may need to run \"manage.py migrate\".")
+
+    def register_action(self, action, entry, **kwargs):
+        """
+        Registers an action at this registry, so it will appear on the menu
+        and can be reversed at the cruds.
+        :param action: Action type
+        :param entry: The menu entry where it will appear
+        :param kwargs: Arguments (besides model) that'll be passed to the view_factory of the action
+        :return: A Django URL pattern that should be added to the patterns of a urls.py module
+        """
+        from django.contrib.auth.models import User
+        module_name = _caller_urls_module()
+        model = entry.model
+        if not module_name:
+            raise Exception("Unidentified python module registering " + str(model))
+        if not registry._modules.has_key(module_name):
+            logging.error("Module " + module_name + " is not set-up for registering cruds")
+            return None
+
+        module_entry = registry._modules[module_name]
+        module_entry.models.add(model)
+
+        if not entry.url_name:
+            entry.url_name = _model_name(model)
+        entry.action = action
+        entry_url = module_entry.menu_name + '_' + entry.url_name + '_' + action.name
+        entry.url = entry_url
+        model_actions = self._models.get(model, {})
+        if model_actions.has_key(action.name):
+            raise Exception("Action " + action.name + " already registered for model " + str(model))
+        model_actions[action.name] = entry
+        self._models[model] = model_actions
+        return url(r'^%s/%s/%s$' % (entry.url_name, action.name, action.url_parameters),
+                   action.view_factory(model=entry.model, **kwargs), name=entry_url)
+
+
+    def get_url_of_action(self, model, action_name, **kwargs):
+        acts = self._models.get(model)
+        if acts and acts.has_key(action_name):
+            return reverse(acts[action_name].url, kwargs=kwargs)
+
+    def modules(self):
+        return self._modules.values()
+
+    def entry_names(self):
+        return [x.menu_name for x in self._modules.values()]
+
+    def is_controlled(self, model):
+        return self._models.has_key(model)
+
+    def model_entry(self, model):
+        return self._models.get(model)
+
+    def module_models(self, module):
+        return self._modules[module].models
+
+
+registry = _Registry()

+ 26 - 0
src/rapid/templates/rapid/widgets/alternativeForms.html

@@ -0,0 +1,26 @@
+<div>
+<style scoped>
+    .rapid-alternatives{
+        border-width: 1px;
+        border-radius: 10px;
+        border-style: solid;
+        padding: 1em;
+    }
+    .hidden{
+        display: none;
+    }
+</style>
+{% for model, form, selected in alternatives %}
+    <div class="rapid-alternatives {{ name }} {{ model.content_type.pk }}{% if not selected %} hidden{% endif %}">
+        {{ form.as_p }}
+    </div>
+{% endfor %}
+<script>
+    $("#id_{{ selector }}").change(function(){
+        var pk = $(this).val();
+        $("div.{{ name }}").addClass("hidden");
+        $("div.{{ name }}."+pk).removeClass("hidden");
+    });
+    $("div.{{ name }}."+($("#id_{{ selector }}").val())).removeClass("hidden");
+</script>
+</div>

+ 4 - 5
src/rapid/templatetags/rapid_list.py

@@ -1,28 +1,27 @@
 __author__ = 'marcos.medeiros'
 
 from django import template
-from django.utils.safestring import mark_safe
-from django.template import loader, Context
-from django.utils.html import escape
-from rapid.views import registry, ModelData
-from rapid import filters
 
 register = template.Library()
 
 _base = 'rapid/list/'
 
+
 @register.inclusion_tag(_base+'field_header.html')
 def field_header(field):
     return {'f': field}
 
+
 @register.inclusion_tag(_base+'pagination.html', takes_context=True)
 def pagination(context):
     return context
 
+
 @register.inclusion_tag(_base+'show_value.html')
 def show_value(val, val_data):
     return {'val': val, 'val_data': val_data}
 
+
 @register.inclusion_tag(_base+'instance_actions.html')
 def instance_actions(instance):
     return {'o': instance}

+ 2 - 1
src/rapid/templatetags/rapid_menu.py

@@ -4,7 +4,8 @@ import locale
 from django import template
 from django.utils.safestring import mark_safe
 from django.utils.html import escape
-from rapid.views import registry, ModelData
+from rapid.registry import registry
+from rapid.wrappers import ModelData
 from django.utils.translation import to_locale, get_language
 
 register = template.Library()

+ 5 - 43
src/rapid/views.py

@@ -5,12 +5,11 @@ from django.views import generic
 from django.core.exceptions import PermissionDenied
 from django.template import RequestContext
 from django.utils.http import urlquote_plus
-from django.forms import ModelForm
 from django.template import loader
 from django.http import HttpResponse
-from rapid.wrappers import InstanceData, ModelData, FieldData
-from rapid.widgets import RapidSelector, RapidRelationReadOnly
+from rapid.wrappers import InstanceData, ModelData
 from rapid.filters import FilterSet
+from rapidforms import for_model
 import math
 
 
@@ -253,43 +252,6 @@ class ReadView(generic.detail.DetailView):
         return self.render_to_response(context)
 
 
-def _get_form(request, model):
-    default_relations_bare = request.GET.get('default')
-    widgets = []
-    default_relations = []
-    default_relations_fields = []
-    if default_relations_bare:
-        default_relations_fields = default_relations_bare.split(",")
-        default_relations = [(x, int(y)) for (x, y) in (f.split(":") for f in default_relations_fields)]
-        default_relations_fields = [x for x, y in default_relations]
-        for (x, y) in default_relations:
-            f = FieldData(getattr(model, x).field, request)
-            widgets.append((x, RapidRelationReadOnly(f.related_model())))
-    ask_relations = []
-    for f in ModelData(model).local_fields():
-        if f.is_relation() and unicode(f.bare_name()) not in default_relations_fields:
-            ask_relations.append(f)
-    widgets += [(f.bare_name(), RapidSelector(f)) for f in ask_relations if
-                f.related_model().has_permission(request, 'select')]
-    # ModelForm.Meta has attributes with the same names, thus I'll rename them
-    form_model = model
-    form_widgets = dict(widgets)
-
-    class CForm(ModelForm):
-        def __init__(self, *args, **kwargs):
-            initial = kwargs.get('initial', {})
-            for (k, v) in default_relations:
-                initial[k] = v
-            kwargs['initial'] = initial
-            super(CForm, self).__init__(*args, **kwargs)
-
-        class Meta:
-            model = form_model
-            fields = '__all__'
-            widgets = form_widgets
-    return CForm
-
-
 class CreateView(generic.edit.CreateView):
     template_name = 'rapid/bare/create.html'
     action_name = ''
@@ -299,7 +261,7 @@ class CreateView(generic.edit.CreateView):
     fields = '__all__'
 
     def request_form(self, request):
-        return _get_form(request, self.model)
+        return for_model(request, self.model)
 
     def get(self, request, **kwargs):
         context = RequestContext(request).flatten()
@@ -311,7 +273,7 @@ class CreateView(generic.edit.CreateView):
         context['model_data'] = cd
 
         if request.POST:
-            context['form'] = self.request_form(request)(request.POST, request.FILEs)
+            context['form'] = self.request_form(request)(request.POST, request.FILES)
         else:
             context['form'] = self.request_form(request)()
 
@@ -340,7 +302,7 @@ class UpdateView(generic.edit.UpdateView):
         return get_object_or_404(self.model, pk=self.kwargs['pk'])
 
     def request_form(self, request):
-        return _get_form(request, self.model)
+        return for_model(request, self.model)
 
     # noinspection PyMethodOverriding
     def get(self, request, pk, **kwargs):

+ 126 - 105
src/rapid/widgets.py

@@ -1,105 +1,126 @@
-# -*- coding: utf-8 -*-
-
-__author__ = 'marcos.medeiros'
-
-from django.forms import widgets
-from django.template import loader, Context
-
-from rapid.wrappers import ModelData, InstanceData
-
-class RapidReadOnly(widgets.Widget):
-    def __init__(self, *args, **kwargs):
-        super(RapidReadOnly, self).__init__(*args, **kwargs)
-
-    def render(self, name, value, attrs=None):
-        hidden = '<input type="hidden" name="%s" value="%s" ' % (name, value)
-        for a in attrs.keys():
-            hidden += '%s="%s" ' % (a, attrs[a])
-        hidden += '>'
-        return '<span class="data-value">%s</span>%s\n' % (unicode(value), hidden)
-
-    def value_from_datadict(self, data, files, name):
-        return data[name]
-
-class RapidRelationReadOnly(widgets.Widget):
-    def __init__(self, model, *args, **kwargs):
-        super(RapidRelationReadOnly, self).__init__(*args, **kwargs)
-        self.model = ModelData(model)
-
-    def render(self, name, value, attrs=None):
-        hidden = '<input type="hidden" name="%s" value="%s" ' % (name, value)
-        for a in attrs.keys():
-            hidden += '%s="%s" ' % (a, attrs[a])
-        hidden += '>'
-        if hasattr(value, '__iter__'):
-            objs = self.model.default_manager().filter(pk__in=value)
-            ret = ''
-            for o in objs:
-                ret += '<span class="data-value multiple">%s</span>\n' % unicode(o)
-            ret += hidden
-            return ret
-        else:
-            obj = self.model.default_manager().get(pk=value)
-            return '<span class="data-value">%s</span>%s\n' % (unicode(obj), hidden)
-
-    def value_from_datadict(self, data, files, name):
-        return data[name]
-
-
-class RapidSelector(widgets.Select):
-    """
-    Selects one of the target crud type.
-    For ForeignKeyFields and OneToOneFields.
-    If the target is dependent (that means, has only
-    value when linked with this object), only displays
-    an edition link.
-    """
-    def __init__(self, relation, *args, **kwargs):
-        super(RapidSelector, self).__init__(*args, **kwargs)
-        self.relation = relation
-        self.allow_multiple_selected = relation.is_multiple()
-        self.remove_deselected = relation.is_weak()
-
-    def render(self, name, value, attrs=None, choices=()):
-        id = attrs.get('id', name)
-        related = self.relation.related_model()
-        if self.allow_multiple_selected:
-            if value:
-                v = ",".join([str(x) for x in value])
-                selected = related.default_manager().filter(pk__in=value)
-            else:
-                v = ""
-                selected = []
-        else:
-            if value:
-                v = str(value)
-                selected = related.default_manager().get(pk=value)
-            else:
-                v = ""
-                selected = ""
-        select_url = related.select_url()
-        if self.allow_multiple_selected:
-            icon = 'fa-times'
-            if self.remove_deselected:
-                icon = 'fa-trash-o'
-        else:
-            icon = 'fa-search'
-        c = Context({'id': id, 'name': name, 'value': v, 'selected': selected, 'icon': icon, 'select_url': select_url,
-                     'multiple': self.allow_multiple_selected})
-        if self.allow_multiple_selected:
-            t = loader.get_template('rapid/widgets/multiple-selector.html')
-        else:
-            t = loader.get_template('rapid/widgets/single-selector.html')
-        return t.render(c)
-
-    def value_from_datadict(self, data, files, name):
-        val = data.get(name)
-        if self.allow_multiple_selected:
-            if val:
-                return [int(x) for x in val.split(",") if x]
-            return []
-        else:
-            if val:
-                return int(val)
-            return None
-
+# -*- coding: utf-8 -*-
+from django.contrib.contenttypes.models import ContentType
+
+__author__ = 'marcos.medeiros'
+
+from django.forms import widgets
+from django.template import loader, Context
+
+from rapid.wrappers import ModelData
+
+_templates_root = 'rapid/widgets/'
+
+class RapidReadOnly(widgets.Widget):
+    def render(self, name, value, attrs=None):
+        hidden = '<input type="hidden" name="%s" value="%s" ' % (name, value)
+        for a in attrs.keys():
+            hidden += '%s="%s" ' % (a, attrs[a])
+        hidden += '>'
+        return '<span class="data-value">%s</span>%s\n' % (unicode(value), hidden)
+
+    def value_from_datadict(self, data, files, name):
+        return data[name]
+
+class RapidRelationReadOnly(widgets.Widget):
+    def __init__(self, model, *args, **kwargs):
+        super(RapidRelationReadOnly, self).__init__(*args, **kwargs)
+        self.model = ModelData(model)
+
+    def render(self, name, value, attrs=None):
+        hidden = '<input type="hidden" name="%s" value="%s" ' % (name, value)
+        for a in attrs.keys():
+            hidden += '%s="%s" ' % (a, attrs[a])
+        hidden += '>'
+        if hasattr(value, '__iter__'):
+            objs = self.model.default_manager().filter(pk__in=value)
+            ret = ''
+            for o in objs:
+                ret += '<span class="data-value multiple">%s</span>\n' % unicode(o)
+            ret += hidden
+            return ret
+        else:
+            obj = self.model.default_manager().get(pk=value)
+            return '<span class="data-value">%s</span>%s\n' % (unicode(obj), hidden)
+
+    def value_from_datadict(self, data, files, name):
+        return data[name]
+
+
+class RapidSelector(widgets.Select):
+    """
+    Selects one of the target crud type.
+    For ForeignKeyFields and OneToOneFields.
+    If the target is dependent (that means, has only
+    value when linked with this object), only displays
+    an edition link.
+    """
+    def __init__(self, relation, *args, **kwargs):
+        super(RapidSelector, self).__init__(*args, **kwargs)
+        self.relation = relation
+        self.allow_multiple_selected = relation.is_multiple()
+        self.remove_deselected = relation.is_weak()
+
+    def render(self, name, value, attrs=None, choices=()):
+        id = attrs.get('id', name)
+        related = self.relation.related_model()
+        if self.allow_multiple_selected:
+            if value:
+                v = ",".join([str(x) for x in value])
+                selected = related.default_manager().filter(pk__in=value)
+            else:
+                v = ""
+                selected = []
+        else:
+            if value:
+                v = str(value)
+                selected = related.default_manager().get(pk=value)
+            else:
+                v = ""
+                selected = ""
+        select_url = related.select_url()
+        if self.allow_multiple_selected:
+            icon = 'fa-times'
+            if self.remove_deselected:
+                icon = 'fa-trash-o'
+        else:
+            icon = 'fa-search'
+        c = Context({'id': id, 'name': name, 'value': v, 'selected': selected, 'icon': icon, 'select_url': select_url,
+                     'multiple': self.allow_multiple_selected})
+        if self.allow_multiple_selected:
+            t = loader.get_template(_templates_root + 'multiple-selector.html')
+        else:
+            t = loader.get_template(_templates_root + 'single-selector.html')
+        return t.render(c)
+
+    def value_from_datadict(self, data, files, name):
+        val = data.get(name)
+        if self.allow_multiple_selected:
+            if val:
+                return [int(x) for x in val.split(",") if x]
+            return []
+        else:
+            if val:
+                return int(val)
+            return None
+
+
+def rapidAlternativesWidget(alternatives, selector):
+    class RapidAlternativeFormsWidget(widgets.Widget):
+        def render(self, name, value, attrs=None):
+            attrs = attrs if attrs else []
+            c = Context({'name': name,
+                         'value': value,
+                         'attrs': attrs,
+                         'alternatives': alternatives.values(),
+                         'selector': selector,
+                         })
+            t = loader.get_template(_templates_root + 'alternativeForms.html')
+            return t.render(c)
+
+        def value_from_datadict(self, data, files, name):
+            ct_id = data.get(selector)
+            if not ct_id:
+                return None
+            return alternatives.get(int(ct_id))[1]
+
+    return RapidAlternativeFormsWidget

+ 369 - 351
src/rapid/wrappers.py

@@ -1,351 +1,369 @@
-# -*- coding: utf-8 -*-
-
-__author__ = 'marcos.medeiros'
-
-from rapid.registry import registry, Action
-from rapid import filters
-
-import itertools
-from django.db import models
-from rapid import permissions
-
-
-class InstanceData:
-    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 = fields if fields else 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 unicode(self.instance)
-
-    def __str__(self):
-        return str(self.model) + ': ' + str(self.instance.pk)
-
-
-class ModelData:
-    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 unicode(self.model._meta.verbose_name)
-
-    def model_name_plural(self):
-        # noinspection PyProtectedMember
-        return unicode(self.model._meta.verbose_name_plural)
-
-    def default_manager(self):
-        # noinspection PyProtectedMember
-        return self.model._default_manager
-
-    def all_fields(self):
-        r = []
-        relations = []
-        for f in itertools.chain(self.local_fields(), self.related_fields()):
-            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):
-        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 related_fields(self):
-        # noinspection PyProtectedMember
-        return [FieldData(f, self.request) for f in self.model._meta.get_all_related_objects()]
-
-    def is_controlled(self):
-        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 registry.get_url_of_action(self.model, "add")
-
-    def list_url(self):
-        return registry.get_url_of_action(self.model, "list")
-
-    def select_url(self):
-        return registry.get_url_of_action(self.model, "select")
-
-    def actions(self):
-        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).get(action_name)
-        if m:
-            return bool(m.permission.model(request))
-        return False
-
-    def field_by_name(self, field_name):
-        # noinspection PyProtectedMember
-        return FieldData(self.model._meta.get_field(field_name), self.request)
-
-    def __unicode__(self):
-        return unicode(self.model)
-
-    def __str__(self):
-        return 'Model: ' + str(self.model)
-
-
-class FieldData:
-    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() == unicode(field_name):
-                return f
-        return None
-
-    def bare_name(self):
-        return unicode(self.field.name)
-
-    def accessor_name(self):
-        if hasattr(self.field, 'get_accessor_name'):
-            return unicode(self.field.get_accessor_name())
-        return unicode(self.field.name)
-
-    def name(self):
-        if self.is_auto() and self.is_relation():
-            return self.related_model().model_name_plural() + u' - ' + self.related_field().name()
-        if hasattr(self.field, "verbose_name"):
-            return unicode(self.field.verbose_name)
-        return unicode(self.field.name)
-
-    def name_plural(self):
-        if self.is_auto() and self.is_relation():
-            return self.related_model().model_name_plural() + u' - ' + self.related_field().name_plural()
-        if hasattr(self.field, "verbose_name_plural"):
-            return unicode(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_weak(self):
-        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, "get_related_field"):
-            o = self.field.get_related_field
-            if self.field.one_to_one or self.field.one_to_many:
-                if hasattr(o, "required"):
-                    return o.required
-                return True
-        if isinstance(f, models.ForeignKey):
-            # noinspection PyUnresolvedReferences,PyProtectedMember
-            return self.related_model()._meta.pk.name
-        return False
-
-    def filter_html(self):
-        return filters.Filter.selection_type_html(self, self.request)
-
-    def __str__(self):
-        return self.bare_name()
-
-
-class ValueData:
-    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)
+# -*- coding: utf-8 -*-
+from django.contrib.contenttypes.models import ContentType
+
+__author__ = 'marcos.medeiros'
+
+from rapid.registry import registry, Action
+from rapid import filters
+import rapidfields
+
+import itertools
+from django.db import models
+from rapid import permissions
+
+
+class InstanceData:
+    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 unicode(self.instance)
+
+    def __str__(self):
+        return str(self.model) + ': ' + str(self.instance.pk)
+
+
+class ModelData:
+    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 unicode(self.model._meta.verbose_name)
+
+    def model_name_plural(self):
+        # noinspection PyProtectedMember
+        return unicode(self.model._meta.verbose_name_plural)
+
+    def default_manager(self):
+        # noinspection PyProtectedMember
+        return self.model._default_manager
+
+    def all_fields(self):
+        r = []
+        relations = []
+        for f in itertools.chain(self.local_fields(), self.related_fields()):
+            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):
+        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__.iteritems():
+            if isinstance(v, rapidfields.AlternativeData):
+                yield (k, v)
+
+    def related_fields(self):
+        # noinspection PyProtectedMember
+        return [FieldData(f, self.request) for f in self.model._meta.get_all_related_objects()]
+
+    def is_controlled(self):
+        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 registry.get_url_of_action(self.model, "add")
+
+    def list_url(self):
+        return registry.get_url_of_action(self.model, "list")
+
+    def select_url(self):
+        return registry.get_url_of_action(self.model, "select")
+
+    def actions(self):
+        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):
+        # noinspection PyProtectedMember
+        return FieldData(self.model._meta.get_field(field_name), self.request)
+
+    def content_type(self):
+        return ContentType.objects.get_for_model(self.model)
+
+    def __unicode__(self):
+        return unicode(self.model)
+
+    def __str__(self):
+        return 'Model: ' + str(self.model)
+
+
+class FieldData:
+    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() == unicode(field_name):
+                return f
+        return None
+
+    def bare_name(self):
+        return unicode(self.field.name)
+
+    def accessor_name(self):
+        if hasattr(self.field, 'get_accessor_name'):
+            return unicode(self.field.get_accessor_name())
+        return unicode(self.field.name)
+
+    def name(self):
+        if self.is_auto() and self.is_relation():
+            return self.related_model().model_name_plural() + u' - ' + self.related_field().name()
+        if hasattr(self.field, "verbose_name"):
+            return unicode(self.field.verbose_name)
+        return unicode(self.field.name)
+
+    def name_plural(self):
+        if self.is_auto() and self.is_relation():
+            return self.related_model().model_name_plural() + u' - ' + self.related_field().name_plural()
+        if hasattr(self.field, "verbose_name_plural"):
+            return unicode(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_weak(self):
+        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, "get_related_field"):
+            o = self.field.get_related_field
+            if self.field.one_to_one or self.field.one_to_many:
+                if hasattr(o, "required"):
+                    return o.required
+                return True
+        if isinstance(f, models.ForeignKey):
+            # noinspection PyUnresolvedReferences,PyProtectedMember
+            return self.related_model()._meta.pk.name
+        return False
+
+    def filter_html(self):
+        return filters.Filter.selection_type_html(self, self.request)
+
+    def __str__(self):
+        return self.bare_name()
+
+
+class ValueData:
+    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)

+ 25 - 0
testproject/testproject/models.py

@@ -0,0 +1,25 @@
+from django.contrib.contenttypes.models import ContentType
+
+__author__ = 'marcos'
+
+from django.db import models
+
+from rapid import rapidfields
+
+class AltData1(models.Model):
+    name = models.CharField(max_length=30)
+
+class AltData2(models.Model):
+    upd_date = models.DateField(auto_now=True)
+
+class Test1(models.Model):
+    some_text = models.CharField(max_length=30)
+    some_int = models.IntegerField()
+    some_date = models.DateField()
+    some_datetime = models.DateTimeField()
+    some_time = models.TimeField()
+    alt_data_type = rapidfields.AlternativeDataTables((AltData1, AltData2))
+    alt_data_id = models.PositiveIntegerField()
+    alt_data = rapidfields.AlternativeData('alt_data_type', 'alt_data_id')
+
+

+ 83 - 83
testproject/testproject/templates/base.html

@@ -1,83 +1,83 @@
-<!DOCTYPE html>
-<html>
-{% load static from staticfiles %}
-<head lang="en">
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <title>{% block title %}Sistemas de Apoio da ANAC{% endblock %}</title>
-    <link href="{% static 'css/bootstrap.css' %}" rel="stylesheet">
-    <link href="{% static 'css/bootstrap-theme.css' %}" rel="stylesheet">
-    <link href="{% static 'css/font-awesome.css' %}" rel="stylesheet">
-    <link href="{% static 'css/base.css' %}" rel="stylesheet">
-    <script src="{% static 'js/jquery.js' %}"></script>
-    <style>
-        div#head{
-            margin-top: 2em;
-            margin-bottom: 3em;
-            margin-left: 6em;
-        }
-        div#area{
-            vertical-align: top;
-            padding-left: 2em;
-        }
-        div#area > * {
-            display: inline-block;
-        }
-        div#menu{
-            vertical-align: top;
-            margin-top: 40px;
-        }
-        nav{
-            width: 20ex;
-            vertical-align: top;
-        }
-        nav ul {
-            list-style-type: none;
-            padding-left: 0em;
-        }
-        nav ul ul {
-            padding-left: 1em;
-        }
-        nav li.menu-group{
-            padding-bottom: 1em;
-        }
-        nav li.menu-group > div:after{
-            content: "\f0d7";
-            font-family: FontAwesome;
-            margin-left: 4pt;
-        }
-        nav li.menu-group.collapsed > div:after{
-            content: "\f0da";
-        }
-        nav li:not(.menu-group){
-            padding-top: 6pt;
-        }
-        table.object_list > tbody > tr > td{
-            max-width: 30ex;
-            word-wrap: break-word;
-        }
-    </style>
-    {% block extra_headers %}{% endblock %}
-</head>
-<body>
-    <div id="head">
-        <h1>Sistemas de Apoio</h1>
-    </div>
-    <div id="area">
-        {% load rapid_menu %}
-        <div id="menu">
-            {% menu request %}
-            {% if not request.user.is_authenticated %}
-            <p><a href="/login?next={{ request.build_absolute_uri }}" class="btn btn-default">Logar</a></p>
-           {% endif %}
-        </div>
-        <div id="main">
-            {% block body %}
-            {% endblock %}
-        </div>
-    </div>
-    <script src="{% static 'js/bootstrap.js' %}"></script>
-    <script src="{% static 'js/jquery.form.js' %}"></script>
-    <script src="{% static 'js/base.js' %}"></script>
-</body>
-</html>
+<!DOCTYPE html>
+<html>
+{% load static from staticfiles %}
+<head lang="en">
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>{% block title %}Rapid Test Site{% endblock %}</title>
+    <link href="{% static 'css/bootstrap.css' %}" rel="stylesheet">
+    <link href="{% static 'css/bootstrap-theme.css' %}" rel="stylesheet">
+    <link href="{% static 'css/font-awesome.css' %}" rel="stylesheet">
+    <link href="{% static 'css/base.css' %}" rel="stylesheet">
+    <script src="{% static 'js/jquery.js' %}"></script>
+    <style>
+        div#head{
+            margin-top: 2em;
+            margin-bottom: 3em;
+            margin-left: 6em;
+        }
+        div#area{
+            vertical-align: top;
+            padding-left: 2em;
+        }
+        div#area > * {
+            display: inline-block;
+        }
+        div#menu{
+            vertical-align: top;
+            margin-top: 40px;
+        }
+        nav{
+            width: 20ex;
+            vertical-align: top;
+        }
+        nav ul {
+            list-style-type: none;
+            padding-left: 0em;
+        }
+        nav ul ul {
+            padding-left: 1em;
+        }
+        nav li.menu-group{
+            padding-bottom: 1em;
+        }
+        nav li.menu-group > div:after{
+            content: "\f0d7";
+            font-family: FontAwesome;
+            margin-left: 4pt;
+        }
+        nav li.menu-group.collapsed > div:after{
+            content: "\f0da";
+        }
+        nav li:not(.menu-group){
+            padding-top: 6pt;
+        }
+        table.object_list > tbody > tr > td{
+            max-width: 30ex;
+            word-wrap: break-word;
+        }
+    </style>
+    {% block extra_headers %}{% endblock %}
+</head>
+<body>
+    <div id="head">
+        <h1>Rapid Test Site</h1>
+    </div>
+    <div id="area">
+        {% load rapid_menu %}
+        <div id="menu">
+            {% menu request %}
+            {% if not request.user.is_authenticated %}
+            <p><a href="/login?next={{ request.build_absolute_uri }}" class="btn btn-default">Logar</a></p>
+           {% endif %}
+        </div>
+        <div id="main">
+            {% block body %}
+            {% endblock %}
+        </div>
+    </div>
+    <script src="{% static 'js/bootstrap.js' %}"></script>
+    <script src="{% static 'js/jquery.form.js' %}"></script>
+    <script src="{% static 'js/base.js' %}"></script>
+</body>
+</html>

+ 5 - 1
testproject/testproject/urls.py

@@ -1,11 +1,15 @@
 from django.conf.urls import include, url
 from django.contrib.auth.views import login, logout
 from testproject import views
+from testproject import models
+
+from rapid import register
+from rapid import permissions
 
 urlpatterns = [
     url(r'^/?$', views.blank),
     url(r'^login$', login),
     url(r'^logout$', logout, {'next_page': '/'}),
     url(r'^applications/', include('rapid.urls')),
-]
+] + register.model(models.Test1, permissions.to_all(), permissions.to_all())