Django - custom attributes for model fields - django

Is there a way in Django to add custom attributes to a model's fields (without resorting to subclassing fields)?
I would like to only display certain fields in certain sections of my template. This is because eventually each type of field will be displayed in a separate tab. I thought about adding a custom attribute to each field to identify which section/tab it should go in. But, so far, I've had no luck.
I have a few field types:
class Enum(set):
def __getattr__(self, name):
if name in self:
return name
raise AttributeError
FieldTypes = Enum(["one","two","three",])
And a few models:
class Model1(models.Model):
a = models.CharField()
b = models.ForeignKey('Model2')
c = models.IntegerField()
a.type = FieldTypes.one # this obviously doesn't work
b.type = FieldTypes.two # this obviously doesn't work
c.type = FieldTypes.three # this obviously doesn't work
class Model2(models.Model):
d = models.CharField()
And a form:
class Form1(forms.ModelForm):
class Meta:
model = Mode1
And a template:
{% for fieldType in FieldTypes %}
<div class="{{fieldType}}">
{% for field in form %}
{% if field.type = fieldType %}
{{ field }}
{% endif %}
{% endfor %}
</div>
{% endfor %}
But this doesn't work.
Ideas? Or other suggestions for only placing certain fields in certain sections of the page?
Thanks.

In general, I would keep this logic outside of the model class. Models shouldn't be tangled up with presentation elements if you can help it, and choosing which fields to display in a form seems like a presentation concern. Fortunately, the Form class provides a nice, UI-focused layer between the data layer (the model) and the presentation layer (the view and template).
Here's how I've addressed this in the past. In my Form class, I created a list of field groups, each with a title and a list of the names of the fields they contain:
class MyModelForm(forms.ModelForm):
field_groups = (
{'name':'Group One', 'fields':('a', 'b', 'c')},
{'name':'Group Two', 'fields':('d', 'e')},
)
class Meta:
model = MyModel
Then in the template, I looped through the groups, and within that loop conditionally included those fields:
{% for group in form.field_groups %}
<h3 class="groupheader">{{group.name}}</h3>
{% for field in form %}
{% if field.name in group.fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endif %}
{% endfor %}
{% endfor %}
This allows you to control the grouping and display of form fields within the MyModelForm class, which is a reasonable place for presentation logic to live.

It's posible!
class Model1(models.Model):
a = models.CharField()
b = models.ForeignKey('Model2')
c = models.IntegerField()
And a form:
class Form1(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Form1, self).__init__(*args, **kwargs)
self.fields['a'].type = FieldTypes.one
self.fields['b'].type = FieldTypes.two
self.fields['c'].type = FieldTypes.three
class Meta:
model = Mode1

Turns out, that I do want this logic in my model class; the field types are used for more than just working out where/how to display them (though they are used for that too). I have come up with the following solution.
I defined some classes to store a set of field types:
class FieldType(object):
def __init__(self, type=None, name=None):
self._type = type
self._name = name
def getType(self):
return self._type
def getName(self):
return self._name
class FieldTypeList(deque):
def __getattr__(self,type):
for ft in self:
if ft.getType() == type:
return ft
raise AttributeError
FieldTypes = FieldTypeList([FieldType("ONE","The Name Of Field One"),FieldType("TWO","The Name Of Field Two")])
And a few models each of which has a set of mappings from field types to particular field names (In this example, fields 'a', 'b', and 'c' are of type 'ONE' and field 'd' is of type 'TWO'):
class ParentModel(models.Model):
_fieldsByType = {}
a = models.CharField()
b = models.CharField()
def __init__(self, *args, **kwargs):
super(ParentModel, self).__init__(*args, **kwargs)
for ft in FieldTypes:
self.setFieldsOfType(ft, [])
self.setFieldsOfType(FieldTypes.ONE, ['a','b'])
def setFieldsOfType(self,type,fields):
typeKey = type.getType()
if typeKey in self._fieldsByType:
self._fieldsByType[typeKey] += fields
else:
self._fieldsByType[typeKey] = fields
class ChildModel(ParentModel):
_fieldsByType = {} # not really sure why I have to repeat this attribute in the derived class
c = models.CharField()
d = models.CharField()
def __init__(self, *args, **kwargs):
super(ChildModel, self).__init__(*args, **kwargs)
self.setFieldsOfType(FieldTypes. ['c'])
self.setFieldsOfType(FieldTypes. ['d'])
I have a basic form:
class MyForm(forms.ModelForm):
class Meta:
model = ChildModel
And a custom filter to return all fields of a given type from a particular form (note, the accessing the model from the form via its Meta class):
#register.filter
def getFieldsOfType(form,type):
return form.Meta.model._fieldsByType[type.getType()]
And, finally, a template to pull it all together (the template takes MyForm and FieldTypes):
{% for type in types %}
<div id="{{type.getType}}">
{% with fieldsOfType=form|getFieldsOfType:type %}
{% for field in form %}
{% if field.name in fieldsOfType %}
<p>
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</p>
{% endif %}
{% endfor %}
{% endwith %}
</div>
{% endfor %}

Related

queryset when building django form

I am trying to get specific querysets based when a customer-specific form loads, showing only that customer's name (embedded as an ID field), its respective locations and users.
The idea is to select one user and any number of locations from a multichoice box.
I've tried to pass the ID as a kwarg but am getting a KeyError. I've tried the kwarg.pop('id') as found on the web and same issue. Any advice?
forms.py
class LocGroupForm(forms.ModelForm):
class Meta:
model = LocationsGroup
fields = ('group_name', 'slug', 'customer', 'location', 'user_id',)
def __init__(self, *args, **kwargs):
qs = kwargs.pop('id')
super(LocGroupForm, self).__init__(*args, **kwargs)
self.fields['customer'].queryset = Customers.objects.get(pk=qs)
self.fields['location'].queryset = CustomerLocations.objects.filter(customer_id=qs)
self.fields['user_id'].queryset = CustomerUsers.objects.filter(customer_id=qs)
here is my view. it's just a generic view
views.py
class LocGroupCreate(LoginRequiredMixin, CreateView):
form_class = LocGroupForm
template_name = 'ohnet/a_gen_form.html'
the template is a dry template I use for all my forms- admittedly something I mostly stole from a tutorial
{% extends "ohnet/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
{% load static %}
<div class="container">
<h1>{{ title }}</h1>
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="submit" value="Submit">
</form>
</div>
{% endblock content %}
This is the KeyError from the form load.
You need to pass a value for the id when constructing the LocGroupForm, you can do that by overriding get_form_kwargs:
class LocGroupCreate(LoginRequiredMixin, CreateView):
form_class = LocGroupForm
template_name = 'ohnet/a_gen_form.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['id'] = …
return kwargs
You will need to fill in the … that specifies the value passed as id to the form. This might for example be self.request.user.pk, or a URL parameter with self.kwargs['name-of-url-parameter']

Display foriegnkey fields in Django template for a CreateView

I am trying to display a checklist in the CreateView using the values in the ForeignKey fields for descriptions.
models.py
class Structure(models.Model):
name = models.CharField(max_length = 30)
description =models.CharField(max_length = 300, null=True, blank=True)
def __str__(self):
return self.name
class SelectedFramework(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
structure = models.ForegignKey(Structure)
selected = models.BooleanField(default = False)
views.py
class FrameworkCreateView(generic.CreateView):
model = SelectedFramework
fields =['structure', 'selected']
template_name = 'catalogue/structure.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super(FrameworkCreateView, self).form_valid(form)
structure.html
{% extends 'catalogue\base.html' %}
{% block container %}
<h2>{% block title %}Structures{% endblock title %}</h2>
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
<div class="col-sm-10">{{form.structure}} {{form.selected}}</div><br>
{% endfor %}
</div>
</form>
{% endblock %}
The code above works but will display the ForeignKey 'structure' as a dropdown list with the values of __str__. Is there a way to display string for structure.name and structure.description with the checkbox from selected in the CreateView?
In your template use:
{{ form.structure.name }}
{{ form.structure.description}}
You can write custom form, override the save method and create Structure object manually there:
class FrameworkForm(forms.ModelForm):
structure_name = forms.CharField(required=True)
structure_description = forms.CharField(required=False)
class Meta:
model = SelectedFramework
fields = [
'structure_name', 'structure_description', 'selected'
]
def save(self, commit=False):
instance = super(FrameworkForm, self).save(commit=False)
structure = Structure(
name=self.cleaned_data.get('structure_name'),
description=self.cleaned_data.get('structure_description')
)
structure.save()
instance.structure = structure
instance.save()
return instance
Also add form_class = FrameworkForm to your view instead of fields = ['structure', 'selected']
EDIT:
Perhaps you want something like this:
<ul>
{% for structure in form.fields.structure.choices.queryset %}
<li>{{ structure.name }} - {{ structure.description }}</li>
{% endfor %}
</ul>
If you want to get fields by iterating in the template. You have to use-
{% for field in form %}
{{ field }}
{% endfor %}
don't have to use any dot notation to get the field. If you want to get the label of the field you can use {{ field.label}} usually before {{field}}

Show ManyToManyField attributes of a given model in template

How can I show in a template the attributes related by a ManyToManyField of a model?
models.py
class Group(models.Model):
name = models.CharField()
user = models.ManyToManyField(User, related_name='user_group')
class Team(models.Model):
name = models.CharField()
group = models.ManyToManyField(Group, related_name='group_team')
views.py
class Index(ListView):
template_name = 'App/index.html'
model = User
def get_queryset(self):
return User.objects.filter(...)
template
{% for user in user_list %}
{{ user.username }}
{{ user.user_group.name }}
{{ user.user_group.group_team.name }}
{% endfor %}
I can show the username of the user but not the rest of the fields.
Since a ManyToMany relationship may have many objects all I had to do is iterate over them:
{% for group in user.user_group.all %}
{{ group.name }}
{% endfor %}
Update: As Todor suggests adding prefetch_related avoid unnecessary database queries
User.objects.filter(...).prefetch_related('user_group')

Use of related field name in django template

I have tow models like below:
class A(models.Model):
a = models.BooleanField(default=False)
q = models.BooleanField(default=False)
class B(models.Model):
c = models.Foreignkey('A', related_name='bb')
d = models.BooleanField(default=False)
e = models.BooleanField(default=False)
Here is my view:
class Myview(ListView):
model = A
template_name = 'admin/layer.html'
def get_context_data(self, *args, **kwargs):
context = super(ListView, self).get_context_data(*args, **kwargs)
context['mylist'] = A.objects.filter(bb__e=False)
return context
Everything is working fine except In my template 'admin/layer.html' I am trying this:
{% for list in mylist %}
{{ list.bb.d }}
{% endfor %}
but I do not get any value for {{ list.bb.d }} Can I use related field name in this way in django template ?
Note that list.bb will only give you the RelatedManager. Here an instance of A can be related to multiple instances of B.
So to get them all you need to use following syntax:
{% for a_obj in mylist %}
{% for b_obj in a_obj.bb.all %}
{{ b_obj }}
{% endfor %}
{% endfor %}
More details provided here:
You can override the FOO_set name by setting the related_name parameter in the ForeignKey definition. For example, if the Entry model was altered to blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'), the above example code would look like this:
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.

ModelMultipleChoiceField' object has no attribute 'to_field_name'

These are my files:
models.py:
class Pierwszy(models.model):
name = models.CharField(max_length=15,blank =True, null= True)
extra = models.CharField(max_length=15,blank =True, null= True)
kids = models.ManyToManyField('Pierwszy', related_name="pierwszy_children", null=True, blank=True)
class Drugi(Pierwszy):
ext_name = models.CharField(max_length=15,blank =True, null= True)
views.py:
class DrugiForm(ModelForm):
def __init__(self, *args, **kwargs):
super(DrugiForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
if instance.name is not None:
self.fields['name'].widget.attrs['readonly'] = True
class Meta:
model = Drugi
fields = ('ext_name','name','kids','extra')
widgets = {
'kids' : forms.ModelMultipleChoiceField(queryset=None, widget=forms.CheckboxSelectMultiple()),
}
hidden = {
'extra'
}
template:
<form method="post">{% csrf_token %}
{{ form.non_field_errors }}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Send message" /></p>
</form>
On this stage I just want to see the current state of object. I don't want to edit fields.
I use queryset=None for kids' widget, because I don't want to show all possibilities, just show list of names (name field) connected to instance.
i'm not sure where should I add filter to queryset (in widget def or in init), but the biggest problem is that, whatever I do, I get
ModelMultipleChoiceField' object has no attribute 'to_field_name'
And I'm stacked now. On Google there's only one case, but this is about overriding the widget/Field - which is not my case.
The widgets dictionary expects the values to be widget instances such as TextArea(), TextInput(), etc.
If you want to use forms.ModelMultipleChoiceField, you could do something like this
class DrugiForm(ModelForm):
kids = forms.ModelMultipleChoiceField(queryset=Pierwszy.objects.none(), widget=forms.CheckboxSelectMultiple())
def __init__(self, *args, **kwargs):
super(DrugiForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
if instance.name is not None:
self.fields['name'].widget.attrs['readonly'] = True
class Meta:
model = Drugi
fields = ('ext_name','name','kids','extra')