Skip to content

Commit

Permalink
added support for related entities
Browse files Browse the repository at this point in the history
  • Loading branch information
faxad committed Jun 26, 2017
1 parent 5a0c109 commit 84723b9
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
__pycache__/
local_settings.py
db.sqlite3
media
media
.vscode/
58 changes: 49 additions & 9 deletions activflow/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from importlib import import_module

from django.apps import apps
from django.forms import inlineformset_factory
from django.forms.models import modelform_factory


Expand Down Expand Up @@ -64,12 +65,15 @@ def get_request_params(what, request=None, **kwargs):

# Model and Form Helpers

def get_app_model_as_params(**kwargs):
"""Returns a list of app and model name as params"""
return [get_request_params(
key, **kwargs) for key in ('app_name', 'model_name')]


def get_model(**kwargs):
"""Returns model"""
args = [get_request_params(
key, **kwargs) for key in ('app_name', 'model_name')]

args = get_app_model_as_params(**kwargs)
return apps.get_model(*args)


Expand All @@ -81,24 +85,33 @@ def get_model_instance(**kwargs):
def get_custom_form(**kwargs):
"""Returns custom form instance"""
try:
args = [get_request_params(
key, **kwargs) for key in ('app_name', 'model_name')]
args = get_app_model_as_params(**kwargs)
config = form_config(*args)
return getattr(import_module(
'{}.forms'.format(apps.get_app_config(args[0]).name)), config)
except (AttributeError, KeyError):
return None


def get_form_config(callee, what, **kwargs):
"""Returns form config"""
args = get_app_model_as_params(**kwargs)
return activity_config(*args)[what]


def get_form_fields(operation, field_config):
"""Returns form fields"""
return [field for field in field_config if operation in field_config[field]]


def get_form_instance(**kwargs):
"""Returns form instance"""
callee = type(inspect.currentframe().f_back.f_locals['self']).__name__
operation = 'create' if 'Create' in callee else 'update'

try:
field_config = activity_config(*[get_request_params(
key, **kwargs) for key in ('app_name', 'model_name')])
fields = [field for field in field_config
if operation in field_config[field]]
config = get_form_config(callee, 'Fields', **kwargs)
fields = get_form_fields(operation, config)
except KeyError:
fields = [field for field in (
field.name for field in get_model(**kwargs)().class_meta.
Expand All @@ -113,6 +126,33 @@ def get_form_instance(**kwargs):
return modelform_factory(get_model(**kwargs), **arguments)


def get_formset_instances(extra=0, **kwargs):
"""Returns a list of formset instances"""
callee = type(inspect.currentframe().f_back.f_locals['self']).__name__
operation = 'create' if 'Create' in callee else 'update'

related_fields = {}
relation_config = get_form_config(callee, 'Relations', **kwargs)

for relation in relation_config:
field_config = relation_config[relation]
related_fields[relation] = get_form_fields(operation, field_config)

def get_related_model(relation):
args = get_app_model_as_params(**kwargs)
args.pop()
args.append(relation)

return apps.get_model(*args)

return [inlineformset_factory(
get_model(**kwargs),
get_related_model(relation),
fields=related_fields[relation],
extra=extra
) for relation in related_fields]


def get_errors(form_errors):
"""Returns compiled form errors"""
error_list = []
Expand Down
4 changes: 2 additions & 2 deletions activflow/core/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-08-04 20:37
# Generated by Django 1.11.2 on 2017-06-22 13:15
from __future__ import unicode_literals

from django.conf import settings
Expand All @@ -12,8 +12,8 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('auth', '0007_alter_validators_add_error_messages'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('auth', '0008_alter_user_username_max_length'),
]

operations = [
Expand Down
2 changes: 1 addition & 1 deletion activflow/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def rollback(self):

class AbstractActivity(AbstractEntity):
"""Common attributes for all activities"""
task = OneToOneField(Task)
task = OneToOneField(Task, null=True)

class Meta(object):
abstract = True
Expand Down
2 changes: 1 addition & 1 deletion activflow/core/templatetags/core_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def activity_data(context, instance, option):
model = type(instance)

try:
config = activity_config(app, model.__name__)
config = activity_config(app, model.__name__)['Fields']
except KeyError:
fields = [field for field in (
(field.name, field.verbose_name) for field in instance.class_meta.
Expand Down
14 changes: 13 additions & 1 deletion activflow/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
get_model,
get_model_instance,
get_form_instance,
get_formset_instances,
get_request_params,
flow_config
)
Expand Down Expand Up @@ -90,7 +91,7 @@ class CreateActivity(AccessDeniedMixin, generic.View):
def get(self, request, **kwargs):
"""GET request handler for Create operation"""
form = get_form_instance(**kwargs)
context = {'form': form}
context = {'form': form, 'formsets': get_formset_instances(extra=2, **kwargs)}

denied = self.check(request, **kwargs)
return denied if denied else render(
Expand All @@ -105,6 +106,11 @@ def post(self, request, **kwargs):

if form.is_valid():
instance = model(**form.cleaned_data)
instance = form.save()

for formset in get_formset_instances(**kwargs):
formset = formset(request.POST, instance=instance)
formset.save() if formset.is_valid() else None

if instance.is_initial:
instance.initiate_request(request.user, app_title)
Expand All @@ -131,8 +137,10 @@ def get(self, request, **kwargs):
"""GET request handler for Update operation"""
instance = get_model_instance(**kwargs)
form = get_form_instance(**kwargs)
formsets = get_formset_instances(**kwargs)
context = {
'form': form(instance=instance),
'formsets': [formset(instance=instance) for formset in formsets],
'object': instance,
'next': instance.next_activity()
}
Expand All @@ -153,6 +161,10 @@ def post(self, request, **kwargs):
if form.is_valid():
form.save()

for formset in get_formset_instances(**kwargs):
formset = formset(request.POST, instance=instance)
formset.save() if formset.is_valid() else None

if 'save' in request.POST:
redirect_to_update = True
instance.update()
Expand Down
18 changes: 18 additions & 0 deletions activflow/templates/core/widgets/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@
</div>
</fieldset>
{% endfor %}

{% for formset in formsets %}
<fieldset class="form-group">
<label class="col-sm-2 control-label">{{ formset.form.instance.title }}:</label>
</fieldset>
{% for form in formset %}
{{ formset.management_form }}
{% for field in form %}
<fieldset class="form-group">
{{ field|label_with_class:"col-sm-2 control-label" }}
<div class="col-sm-10">
{{ field }}
</div>
</fieldset>
{% endfor %}
{% endfor %}
{% endfor %}

{% wysiwyg_form_fields as wysuwyg_fields %}
{% for wysuwyg_field in wysuwyg_fields %}
<script type="text/javascript">CKEDITOR.replace('id_{{wysuwyg_field}}')</script>
Expand Down
30 changes: 18 additions & 12 deletions activflow/tests/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@

ACTIVITY_CONFIG = odict([
('Foo', odict([
('subject', ['create', 'update', 'display']),
('bar', ['create', 'update', 'display']),
('baz', ['create', 'update', 'display']),
('qux', ['create', 'update', 'display']),
('creation_date', ['display']),
('last_updated', ['display'])
('Fields', odict([
('subject', ['create', 'update', 'display']),
('bar', ['create', 'update', 'display']),
('baz', ['create', 'update', 'display']),
('qux', ['create', 'update', 'display']),
('creation_date', ['display']),
('last_updated', ['display'])
])),
('Relations', odict([
('FooLineItem', odict([
('plugh', ['create', 'update', 'display']),
('thud', ['create', 'update', 'display'])
])),
('FooMoreLineItem', odict([
('plughmore', ['create', 'update', 'display']),
('thudmore', ['create', 'update', 'display'])
]))
]))
])),
# ('Corge', odict([
# ('grault', ['create', 'update', 'display']),
# ('thud', ['create', 'update', 'display']),
# ('creation_date', ['display']),
# ('last_updated', ['display'])
# ])),
])

# config for Corge commented out to demonstrate that config is optional
Expand Down
2 changes: 1 addition & 1 deletion activflow/tests/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-08-04 20:37
# Generated by Django 1.11.2 on 2017-06-22 13:15
from __future__ import unicode_literals

import activflow.tests.validators
Expand Down
33 changes: 33 additions & 0 deletions activflow/tests/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Model definition for test workflow"""

from django.db import models
from django.db.models import (
CharField,
ForeignKey,
IntegerField,
TextField)

Expand All @@ -20,6 +22,37 @@ def clean(self):
"""Custom validation logic should go here"""
pass

class FooLineItem(models.Model):
"""Sample representation of Foo Line Item"""
foo = ForeignKey(Foo, related_name="lines")
plugh = CharField("Plugh", max_length=200, validators=[validate_initial_cap])
thud = CharField(verbose_name="Thud", max_length=30, choices=(
('GR', 'Grault'), ('GA', 'Garply')))

@property
def title(self):
"""Returns entity title"""
return self.__class__.__name__

def clean(self):
"""Custom validation logic should go here"""
pass

class FooMoreLineItem(models.Model):
"""Sample representation of FooMore Line Item"""
foo = ForeignKey(Foo, related_name="morelines")
plughmore = CharField("Plughmore", max_length=200, validators=[validate_initial_cap])
thudmore = CharField(verbose_name="Thudmore", max_length=30, choices=(
('GR', 'Grault'), ('GA', 'Garply')))

@property
def title(self):
"""Returns entity title"""
return self.__class__.__name__

def clean(self):
"""Custom validation logic should go here"""
pass

class Corge(AbstractActivity):
"""Sample representation of Corge activity"""
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
django_extensions==1.5.9
django_extensions==1.7.9

0 comments on commit 84723b9

Please sign in to comment.