Creating Forms dynamically from a model - python

I'm learning Django as I go right now, but I'm trying to do something more dynamically.
Basically I've got multiple models defined as so:
class Group(models.Model):
name = models.CharField(max_length=255)
type = models.CharField(max_length=255) # Ignore this, is used somewhere else
class User(models.Model):
name = models.CharField(max_length=255)
group = models.ForeignKey(Group, verbose_name='group', on_delete=models.CASCADE)
(there are more models than these two)
I feel like writing a different view for each of these models isn't really a Django way to do it, but I'm struggling to find a different way. Basically I want to automatically create forms depending on what fields the model has.
So on /department/ there should be an text input for name and type.
But on /user/ there should be an text input for name and an selection for group.
All that rather in the same template. If possible ofcourse.
If it isn't possible, what's the best way to do this? Since creating a different template for each model doesn't seem right.
EDIT:
I've now got my CreateView working (very basic still). Now I want to create a ListView in a similar fashion. I've seen this issue, Iterate over model instance field names and values in template but it wasn't able to help me out...
forms.py
class ModelCreate(CreateView):
fields = '__all__'
template_name = 'polls/models/model_create.html'
def get_form(self, form_class=None):
try:
self.model = apps.get_model('polls', self.kwargs['model'])
except LookupError:
raise Http404('%s is not a valid model.' % self.kwargs['model'])
return super(ModelCreate, self).get_form(form_class)
model_create.html
{% extends 'polls/core.html' %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
Would it be possible to create a ListView in a similar fashion? So that I get field names and values dynamically (bit like {{ form }})

Related

Django: submit only one form created with for loop

In my code I'm using a class CreateView with a ListView. I'm also using a for loop to show all the possible dates available (that are created in the StaffDuty models). My user should be able to just book a single date.
My problem is that I'm not able to save a single appointment, I have to compile all the form showed in my list to be able to submit. How can I solve this?
models.py
class UserAppointment(models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
staff = models.ForeignKey(StaffDuty, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
event_name = models.CharField(max_length=255)
date_appointment = models.DateField(null=True)
def __str__(self):
return self.event.name | str(self.staff.date_work)
def get_absolute_url(self):
return reverse('home')
views.py
class UserAppointmentAddView(CreateView):
model = UserAppointment
form_class = UserAppointmentForm
template_name = "reservation.html"
def form_valid(self, form):
form.instance.user = self.request.user.userinformation
def get_context_data(self, **kwargs):
kwargs['object_list'] = StaffDuty.objects.order_by('id')
return super(UserAppointmentAddView, self).get_context_data(**kwargs)
html
<div class="container">
<form method="post">
{% csrf_token %}
{% for appointment in object_list %}
<span>{{ form.staff }}</span>
<span>{{ form.event }}</span>
<span>{{ form.morning_hour }}</span>
<span>{{ form.afternoon_hour }}</span>
<div class="primary-btn">
<input type="submit" value="submit" class="btn btn-primary">
</div>
</div>
{% endfor %}
If I understand right, you are generating the same form for each appointment/ model instance, which won't work. What gets POSTed back does not identify which appointment object it refers to. That could be fixed with a hidden object_id field ... which is part of what model formsets do for you. Or you might put it into the value of your multiple submit buttons and pull it out of request.POST if you are happy to handle validation yourself.
The pure-Django solution is to generate a model formset. When it is submitted you would process the form that has changed and ignore the rest. (It will in fact give you lists of changed and unchanged objects).
The other approach would be using JavaScript to populate one form (possibly with hidden fields) when the user clicks on values in a table. Or no form in the template, just POST from JavaScript and validate what comes back through a form (or otherwise)
Formsets and model formsets look fearsomely complicated the first time you try to use them. They aren't really, but the first one you code may present you with a bit of a steep learning curve. It's easier if you can find something working that somebody else has already written, and adapt it.

Django - Custom crispy-forms widget to select from or add-to list

I'm looking for a crispy forms version of this:
Django form with choices but also with freetext option?
Models.py
class Animals(models.Model):
animal_id = models.AutoField(primary_key=True)
animal_name = models.CharField(max_length=100, verbose_name='Animal Name')
forms.py
from django import forms
from django.forms import ModelForm
from .models import Animals
class CustomSelection(Field):
template = 'custom_selectbox.html'
class AnimalsForm(ModelForm):
class Meta:
model = Animals
fields = [
'animal_name',
]
def __init__(self, *args, **kwargs):
super(AnimalsForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.layout = Layout(
Div(
Fieldset(CustomSelection('animal_name'))
FormActions(
Button('submit','Submit')
)
)
)
So if I have:
custom_selectbox.html
{% load crispy_forms_field %}
<div class="form-group">
{{ field.label_tag }}
{% crispy_field field 'class' 'custom-select' %}
</div>
this renders a box OK, which is the first thing I want.
Next, I would like to know if it's possible to somehow inject all of the existing animal_names,
something like this:
{% load crispy_forms_field %}
<input list="options" name="test-field" required="" class="form-control" id="test-field-add">
<datalist id="options">
{% for option in field.subwidgets %}
<option value="{{ option.choice_label }}"/>
{% endfor %}
</datalist>
but with support for crispy-forms. Essentially I want the user to be presented with a list of existing CharField entries - but if the thing they're looking for isn't around, then I want them to be able to add it. Note: it doesn't need to be a charfield - I could do an independent model and use a Foreign Key but if I do that I'm not sure if that means I need to make a separate form to add new entries to every model (the example is animal_name - in reality the form I need to build has lots of equivalent fields). I would accept the reduced control of a charfield over a foreignkey if it is easier for users to add new entries.
I think the datalist approach would work if I'm making the form directly (forgive me, I'm very new to django) but it feels like I'm (hopefully) missing a more django-esque solution. Ideally one that is compatible with my existing crispy-forms. These forms are very large, so I'm using the Layout() helper heavily, so I would really like to avoid re-creating all of this manually.
I have seen this: Django crispy forms work with custom widgets?
but it's very old and I don't understand django well enough to know if the answer still applies.
I have also seen: https://leaverou.github.io/awesomplete/ - this looks equivalent to the datalist approach.
Then finally, https://pypi.org/project/django-awesomplete/ - the only example is for the admin panel so I'm not sure if I can do the same thing with crispy-forms.
I did it using django-floppyforms. I found the solution here: Django form with choices but also with freetext option?
My code:
forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('sexo', 'data_nascimento', 'foto', 'sobre_mim', 'telefone', 'paroquia','cidade','estado', 'cep', 'possui_filhos', 'facebook', 'instagram')
widgets = {
'cidade': floppyforms.widgets.Input(datalist=CIDADES, attrs={'autocomplete': 'off'}),
}
Note:
CIDADES is my list containing cities.
atrrs is optional.

Django,getting only unique values from related object

I wish I could render in my website list of most used tags,which I do.The problem is,displayed tags often repeat and I'm sure it would be much better if only unique tags were displayed.
In my model I have writen method unique_tags
class ArticleTag(models.Model):
article = models.ForeignKey('ep.Article', on_delete=models.CASCADE, related_name='tags')
tag = models.ForeignKey('ep.Tag', on_delete=models.CASCADE)
def unique_tags(self):
return self.objects.values_list('tag').distinct()
and when I tested it in python shell it works fine.But it doesnt render any tag.My template looks like this:
<div class="widget-tags">
<ul>
{% for prev_article in object.articles.all %}
{% for article_tag in prev_article.article.tags.all.unique_tags %}
<li>{{article_tag.tag.verbose_name}}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
Where object is from model that makes relation with Article table and Field table,so with object.articles.all I've got all instances of articles that are related to specific field.I use detail view in my views.
So,my first question is,Is this valid approach? I mean,adding new method in model class, or perhaps I should add this in views?.Also I still not comfortable with django template language so maybe there is problem.And I know there is this filter in template like {{ some_object | function}} but I've read that it is good practice to keep as little logic in template as it is possible.
I would add logic for this on the Article level if you want to do this:
class Article(models.Model):
def unique_tags(self):
return Tag.objects.filter(articletag__article=self).distinct()
and then query this with:
{% for article_tag in prev_article.unique_tags %}
<li>{{article_tag.tag.verbose_name}}</li>
{% endfor %}
By using a .values_list, you will obtain the value of the primary key, so usually an int (or another primitive type given you defined a primary key yourself).
That being said, I think it is better to simply make sure that this can never happen, by adding a unique_together [Django-doc] constraint in the model:
class ArticleTag(models.Model):
article = models.ForeignKey(
'ep.Article',
on_delete=models.CASCADE,
related_name='tags'
)
tag = models.ForeignKey('ep.Tag', on_delete=models.CASCADE)
class Meta:
unique_together = ('article', 'tag')
So now you can simply not tag the same article with the same tag twice. So once this is enforced, you can simply use:
<!-- given unique_together is enforced -->
{% for article_tag in prev_article.tags.all %}
<li>{{article_tag.tag.verbose_name}}</li>
{% endfor %}
and you can then use .prefetch_related(..) to load all the related objects with a constant number of queries.

Access to models with a foreign key in the template

I have a profile model with a one-to-one relationship to the User model so I can access to both models in the templates tanks to the user variable like this:
template.html
{% if user.profile.phone == 1234567890 %}
Show something
{% endif %}
That works fine, the condition gives True and show something but I have too the models Property and User_Property, the User_Property model have as Foreignkey the ids from User and Property.
models.py
class Property(models.Model):
name = models.CharField(max_length=50, unique=True)
class User_Property(models.Model):
us = models.ForeignKey(User, related_name='up_us')
prop = models.ForeignKey(Property, related_name='up_prop')
So if I try to access to the User_Property model like this:
{% if user.user_property.prop == 1 %}
Show something
{% endif %}
I can't access it shows nothing like it was False even when it's True, I have tried with user.user_property.prop_id == 1 too. It is beacause the relationship with the Profile model was made with the OneToOneField and the relationship with User_Property was made with the ForeignKey field and I need to pass in the context the User_Property model?
And it is possible to access to Property model like if I use a JOIN SQL statement in the template? something like this:
{% if user.user_property.property.name == 'the name of the property' %}
Show something
{% endif %}
Sorry for the long Post but I tried to add all the need info.
EDIT: Ok if someone need something similar this is what I did to solve the problem.
Create a context_processor.py to return a instance of User_Property and add it to my settings.py in this way I can access to the instance in all my templates even if I don't pass it as context in the views.
context_processors.py
from App_name.models import User_Property
from django.contrib.auth.models import User
def access_prop(request):
user = request.user.id #add the .id to no have problems with the AnonymousUser in my logg-in page
user_property = User_Property.objects.filter(us=user).values_list('prop', flat=True) #this stores the list of all the properties for the logg-in user
return {
'user_property': user_property,
}
settings.py
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS += ('App_name.context_processors.access_prop',)
Then in the template check if the user have a especific property
template.html
{% if 'property name' in user_property %}
Show something
{% else %}
This is not for you
{% endif %}
To can check in especific for the name instead of the id just add to_field='name' in my prop field in the model User_Property like this: prop = models.ForeignKey(Property, related_name='up_prop', to_field='name').
From the docs
You should use the related_name that you set in the ForeignKey and the built-in methods of the relationships:
try this:
user.up_us.filter(prop__name="the name")
EDIT
for using the .filter(prop__name="the name") method you have to do it in a .py file.
Give this a try: {% if user.user_property.prop.id == 1 %}
You've set related_name in us = models.ForeignKey(User, related_name='up_us'), so you need to use it
{% if user.up_us.prop.name == 'the name of the property' %}
Show something
{% endif %}
This answer has a good explanation of how to use and what related_name for.
And try to exclude to much logic from templates.

Getting and displaying related objects in Django

I know this is simple, but I can't get my head around how to join some models together to display in my template in Django. I have "groups" that can have several "contacts".
So far I've got:
class Group(models.Model):
group_name = models.CharField()
class Contact(models.Model):
contact_name = models.ForeignKey(Group)
In my view, at first I assumed that simply getting my groups would also get any attached contacts, however that doesn't appear to be happening as expected:
def get_queryset(self):
groups = Group.objects.all()
return groups
I was expecting to do something like this in my template:
{% for group in groups %}
<h2>{{ group.group_name }}</h2>
{% for c in group.contact %}
<h3>{{ c.contact_name }}</h3>
{% endfor %}
{% endfor %}
This isn't working - what am I doing wrong? What is the correct query in my view to make sure the contact(s) for each group is getting retrieved?
Well, it looks like you've got some of your code from a different place so just so you can fully understand, you can do this in 2 different ways:
1) To access a related object of any kind, being a simple ForeignKey or ManyToMany you just need to go from the opposite model and use _set like this example:
class Group(models.Model):
group_name = models.CharField()
class Contact(models.Model):
contact_name = models.ForeignKey(Group)
{{ group.contact_set.all }}
2) You can set up a name different than the default _set changing Contact like this:
class Contact(models.Model):
contact_name = models.ForeignKey(Group, related_name='contacts')
So, related_name kwarg set a new name for you instead of the _set one:
{{ group.contacts.all }}
I hope I manage to make it clearer about simple access on models related objects.

Categories

Resources