Skip to content

Commit e177f83

Browse files
author
Jan Schrewe
committed
Now uses mongotools MongoFormFieldGenerator. unique fields are validated. Still now validation of unique_with.
1 parent e6b09ed commit e177f83

File tree

2 files changed

+46
-183
lines changed

2 files changed

+46
-183
lines changed

mongodbforms/documents.py

Lines changed: 40 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.utils.datastructures import SortedDict
22

3-
from django.forms.forms import BaseForm, get_declared_fields, NON_FIELD_ERRORS
3+
from django.forms.forms import BaseForm, get_declared_fields, NON_FIELD_ERRORS, pretty_name
44
from django.forms.widgets import media_property
55
from django.core.exceptions import FieldError, ValidationError as DjangoValidationError
66
from django.core.validators import EMPTY_VALUES
@@ -108,7 +108,7 @@ def document_to_dict(instance, fields=None, exclude=None):
108108
data[f.name] = getattr(instance, f.name)
109109
return data
110110

111-
def fields_for_document(document, fields=None, exclude=None, widgets=None, formfield_callback=None):
111+
def fields_for_document(document, fields=None, exclude=None, widgets=None, formfield_callback=None, field_generator=MongoFormFieldGenerator):
112112
"""
113113
Returns a ``SortedDict`` containing form fields for the given model.
114114
@@ -121,7 +121,8 @@ def fields_for_document(document, fields=None, exclude=None, widgets=None, formf
121121
"""
122122
field_list = []
123123
ignored = []
124-
field_generator = MongoFormFieldGenerator()
124+
if isinstance(field_generator, type):
125+
field_generator = field_generator()
125126
for f in document._fields.itervalues():
126127
if isinstance(f, (ObjectIdField, ListField)):
127128
continue
@@ -158,7 +159,6 @@ class ModelFormOptions(object):
158159
def __init__(self, options=None):
159160
self.document = getattr(options, 'document', None)
160161
self.model = self.document
161-
# TODO: Move AdminOptions to mongoforms
162162
if isinstance(self.model._meta, dict):
163163
self.model._admin_opts = AdminOptions(self.model)
164164
self.model._meta = self.model._admin_opts
@@ -186,9 +186,11 @@ def __new__(cls, name, bases, attrs):
186186

187187
opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
188188
if opts.document:
189+
formfield_generator = getattr(opts, 'formfield_generator', MongoFormFieldGenerator)
190+
189191
# If a model is defined, extract form fields from it.
190192
fields = fields_for_document(opts.document, opts.fields,
191-
opts.exclude, opts.widgets, formfield_callback)
193+
opts.exclude, opts.widgets, formfield_callback, formfield_generator)
192194
# make sure opts.fields doesn't specify an invalid field
193195
none_document_fields = [k for k, v in fields.iteritems() if not v]
194196
missing_fields = set(none_document_fields) - \
@@ -337,12 +339,32 @@ def _post_clean(self):
337339

338340
def validate_unique(self):
339341
"""
340-
Calls the instance's validate_unique() method and updates the form's
341-
validation errors if any were raised.
342-
343-
FIXME: Does nothing at the moment.
342+
Validates unique constrains on the document.
343+
unique_with is not checked at the moment.
344344
"""
345-
return
345+
errors = []
346+
exclude = self._get_validation_exclusions()
347+
for f in self.instance._fields.itervalues():
348+
if f.unique and f.name not in exclude:
349+
filter_kwargs = {
350+
f.name: getattr(self.instance, f.name)
351+
}
352+
qs = self.instance.__class__.objects().filter(**filter_kwargs)
353+
# Exclude the current object from the query if we are editing an
354+
# instance (as opposed to creating a new one)
355+
if self.instance.pk is not None:
356+
qs = qs.filter(pk__ne=self.instance.pk)
357+
if len(qs) > 0:
358+
message = _(u"%(model_name)s with this %(field_label)s already exists.") % {
359+
'model_name': unicode(capfirst(self.instance._meta.verbose_name)),
360+
'field_label': unicode(pretty_name(f.name))
361+
}
362+
err_dict = {f.name: [message]}
363+
self._update_errors(err_dict)
364+
errors.append(err_dict)
365+
366+
return errors
367+
346368

347369

348370
def save(self, commit=True):
@@ -501,84 +523,14 @@ def clean(self):
501523
self.validate_unique()
502524

503525
def validate_unique(self):
504-
return
505-
# # Collect unique_checks and date_checks to run from all the forms.
506-
# all_unique_checks = set()
507-
# all_date_checks = set()
508-
# for form in self.forms:
509-
# if not hasattr(form, 'cleaned_data'):
510-
# continue
511-
# exclude = form._get_validation_exclusions()
512-
# unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
513-
# all_unique_checks = all_unique_checks.union(set(unique_checks))
514-
# all_date_checks = all_date_checks.union(set(date_checks))
515-
#
516-
# errors = []
517-
# # Do each of the unique checks (unique and unique_together)
518-
# for uclass, unique_check in all_unique_checks:
519-
# seen_data = set()
520-
# for form in self.forms:
521-
# # if the form doesn't have cleaned_data then we ignore it,
522-
# # it's already invalid
523-
# if not hasattr(form, "cleaned_data"):
524-
# continue
525-
# # get data for each field of each of unique_check
526-
# row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data])
527-
# if row_data and not None in row_data:
528-
# # if we've aready seen it then we have a uniqueness failure
529-
# if row_data in seen_data:
530-
# # poke error messages into the right places and mark
531-
# # the form as invalid
532-
# errors.append(self.get_unique_error_message(unique_check))
533-
# form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
534-
# del form.cleaned_data
535-
# break
536-
# # mark the data as seen
537-
# seen_data.add(row_data)
538-
# # iterate over each of the date checks now
539-
# for date_check in all_date_checks:
540-
# seen_data = set()
541-
# uclass, lookup, field, unique_for = date_check
542-
# for form in self.forms:
543-
# # if the form doesn't have cleaned_data then we ignore it,
544-
# # it's already invalid
545-
# if not hasattr(self, 'cleaned_data'):
546-
# continue
547-
# # see if we have data for both fields
548-
# if (form.cleaned_data and form.cleaned_data[field] is not None
549-
# and form.cleaned_data[unique_for] is not None):
550-
# # if it's a date lookup we need to get the data for all the fields
551-
# if lookup == 'date':
552-
# date = form.cleaned_data[unique_for]
553-
# date_data = (date.year, date.month, date.day)
554-
# # otherwise it's just the attribute on the date/datetime
555-
# # object
556-
# else:
557-
# date_data = (getattr(form.cleaned_data[unique_for], lookup),)
558-
# data = (form.cleaned_data[field],) + date_data
559-
# # if we've aready seen it then we have a uniqueness failure
560-
# if data in seen_data:
561-
# # poke error messages into the right places and mark
562-
# # the form as invalid
563-
# errors.append(self.get_date_error_message(date_check))
564-
# form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
565-
# del form.cleaned_data
566-
# break
567-
# seen_data.add(data)
568-
# if errors:
569-
# raise ValidationError(errors)
570-
571-
def get_unique_error_message(self, unique_check):
572-
if len(unique_check) == 1:
573-
return ugettext("Please correct the duplicate data for %(field)s.") % {
574-
"field": unique_check[0],
575-
}
576-
else:
577-
return ugettext("Please correct the duplicate data for %(field)s, "
578-
"which must be unique.") % {
579-
"field": get_text_list(unique_check, unicode(_("and"))),
580-
}
581-
526+
errors = []
527+
for form in self.forms:
528+
if not hasattr(form, 'cleaned_data'):
529+
continue
530+
errors += form.validate_unique()
531+
532+
if errors:
533+
raise ValidationError(errors)
582534
def get_date_error_message(self, date_check):
583535
return ugettext("Please correct the duplicate data for %(field_name)s "
584536
"which must be unique for the %(lookup)s in %(date_field)s.") % {

mongodbforms/util.py

Lines changed: 6 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from django import forms
22

3-
from fields import ReferenceField
3+
from mongotools.forms.fields import MongoFormFieldGenerator as MongotoolsGenerator
44

5+
from fields import ReferenceField
56
from documentoptions import AdminOptions
67

78
def init_document_options(document):
@@ -14,22 +15,17 @@ def init_document_options(document):
1415
def get_document_options(document):
1516
return AdminOptions(document)
1617

17-
# Taken from django-mongoforms (https://github.com/stephrdev/django-mongoforms)
18-
#
19-
# Copyright (c) 2010, Stephan Jaekel <steph@rdev.info>
20-
# All rights reserved.
21-
class MongoFormFieldGenerator(object):
18+
class MongoFormFieldGenerator(MongotoolsGenerator):
2219
"""This class generates Django form-fields for mongoengine-fields."""
2320

2421
def generate(self, field_name, field):
2522
"""Tries to lookup a matching formfield generator (lowercase
2623
field-classname) and raises a NotImplementedError of no generator
2724
can be found.
2825
"""
29-
if hasattr(self, 'generate_%s' % field.__class__.__name__.lower()):
30-
return getattr(self, 'generate_%s' % \
31-
field.__class__.__name__.lower())(field_name, field)
32-
else:
26+
try:
27+
return super(MongoFormFieldGenerator, self).generate(field_name, field)
28+
except NotImplementedError:
3329
# a normal charfield is always a good guess
3430
# for a widget.
3531
# TODO: Somehow add a warning
@@ -45,89 +41,4 @@ def generate(self, field_name, field):
4541
kwargs['initial'] = field.default
4642

4743
return forms.CharField(kwargs)
48-
49-
50-
def generate_stringfield(self, field_name, field):
51-
if field.regex:
52-
return forms.CharField(
53-
regex=field.regex,
54-
required=field.required,
55-
min_length=field.min_length,
56-
max_length=field.max_length,
57-
initial=field.default
58-
)
59-
elif field.choices:
60-
return forms.ChoiceField(
61-
required=field.required,
62-
initial=field.default,
63-
choices=zip(field.choices, field.choices)
64-
)
65-
elif field.max_length is None:
66-
return forms.CharField(
67-
required=field.required,
68-
initial=field.default,
69-
min_length=field.min_length,
70-
widget=forms.Textarea
71-
)
72-
else:
73-
return forms.CharField(
74-
required=field.required,
75-
min_length=field.min_length,
76-
max_length=field.max_length,
77-
initial=field.default
78-
)
79-
80-
def generate_emailfield(self, field_name, field):
81-
return forms.EmailField(
82-
required=field.required,
83-
min_length=field.min_length,
84-
max_length=field.max_length,
85-
initial=field.default
86-
)
87-
88-
def generate_urlfield(self, field_name, field):
89-
return forms.URLField(
90-
required=field.required,
91-
min_length=field.min_length,
92-
max_length=field.max_length,
93-
initial=field.default
94-
)
95-
96-
def generate_intfield(self, field_name, field):
97-
return forms.IntegerField(
98-
required=field.required,
99-
min_value=field.min_value,
100-
max_value=field.max_value,
101-
initial=field.default
102-
)
103-
104-
def generate_floatfield(self, field_name, field):
105-
return forms.FloatField(
106-
required=field.required,
107-
min_value=field.min_value,
108-
max_value=field.max_value,
109-
initial=field.default
110-
)
111-
112-
def generate_decimalfield(self, field_name, field):
113-
return forms.DecimalField(
114-
required=field.required,
115-
min_value=field.min_value,
116-
max_value=field.max_value,
117-
initial=field.default
118-
)
119-
120-
def generate_booleanfield(self, field_name, field):
121-
return forms.BooleanField(
122-
required=field.required,
123-
initial=field.default
124-
)
125-
126-
def generate_datetimefield(self, field_name, field):
127-
return forms.DateTimeField(
128-
required=field.required,
129-
initial=field.default
130-
)
13144

132-
def generate_referencefield(self, field_name, field):
133-
return ReferenceField(field.document_type.objects)

0 commit comments

Comments
 (0)