Rostgaard.IT

← Home

[DRAFT] Complex Django Model

created: Sun Aug 02 2020

Intro #

If description of my issues etc is not important to you, please click here, to jump to my solution to the problem

For the first time in, maybe my whole software career, i came upon a problem that i couldn't answer with previous experience and i couldn't find anything on the internet to help me with it, so i decided to try and solve the problem my self. This is my documentation as to how i solved it in the hopes that it might help you if you run into a similar problem.

The problem #

I In July of 2020 I asked this question on Stackoverflow.
I definately didn't ask it correctly as some of the details was unnecessary. After trying to reshape the question i think i finally got to the root of the problem:

How do I create a django model with similar data on each field.

This proved to be harder than expected. After discussing the issue with my friend, I decided to

Solution #

The model(s) #

To archieve this we will need to create 2 models.

MyModel #

# my-project/my-app/models.py
class MyModel(models.Model):
field1 = MyModelField()
field2 = MyModelField()
field3 = MyModelField()
...
FieldN = MyModelField()

MyModelField #

# my-project/my-app/models.py 
class MyModelField(models.Model):
field_a = models.IntegerField()
field_b = models.CharField(max_length=128)
...

Clean the model #

Because this model expects all fields on the model to have unique contraints we need to write our own clean() method.

class MyModel(models.Model):
field1 = MyModelField()
....
fieldN = MyModelField()

def clean(self):
pks = [getattr(self, p.name).pk for p in [f for f in self._meta.get_fields()]]
if len(pks) > len(set(pks)):
raise ValidationError(_('MyModelField should be unique on the model.'))

def save(self, *args, **kwargs):
try:
self.clean()
super(MyModel, self).save(*args, **kwargs)
except ValidationError as e:
print(f"Validation error happened trying to save: {self.pk}")
raise ValidationError(_("Model isn't valid, object won't be saved."))

The form(s) #

The views #


def _field_to_form(f, obj):
if obj is not None:
model_instance = gettr(obj, f.name)
else:
model_instance = None

form = {
'title': f.verbose_name,
'help_text': f.help_text,
'id': f.name,
'form': MyModelField(
instance=obj,
prefix=f.name
)
}

def my_view(request):
form_fieldss = [f for f in MyModel._meta.get_fields()]

# Check if the form has already been filled out
try:
obj = MyModel.objects.get(unique_field=unique_check)
except:
obj = None

if request.method == 'POST':
valid_forms = []
invalid_forms = []
forms = []
for f in form_fields:
form = MyModelField(request.POST, prefix=f.name) #the prefix is important to tell the different onetoonefields apart.
if form.is_valid:
valid_forms.append(form)
else:
invalid_forms.append(form)
forms.append(_field_to_form(f, obj))

# If no invalid answers
if len(invalid_forms) == 0:
new_obj = False
if obj == None:
new_obj = True
obj = MyModel(
# Fill out with anything you need when creating the first model, like the user who created it, or time etc.
)

for f in valid_forms:
setattr(obj, f.prefix, f.save())

try:
obj.save()
messages.success(request, f"success message goes here")
except ValidationError as e:
messages.error(request, f"error message about failed save() validation goes here")

else:
messages.error(request, f"error message goes here")


if request.method == 'GET':
forms = []
for f in form_fields:
form = _field_to_form(f, obj)
forms.append(form)

return render(request, 'path/to/template', {'forms': forms})

Finally #

← Home