I have the following models,
### models.py
class Foo(models.Model):
name = models.CharField(max_length=200)
class Bar(models.Model):
foo = models.ForeignKey(Foo)
baz = models.ManyToManyField(Baz, through='Between')
class Baz(models.Model):
name = models.CharField(max_length=200)
class Between(models.Model):
foo = models.ForeignKey(Foo)
bar = models.ForeignKey(Bar)
CHOICES = (
('A', 'A'),
('B', 'B'),
('C', 'C'),
)
value = models.CharField(max_length=1, choices=CHOICES)
and I have the following forms,
### forms.py
class FooForm(forms.ModelForm):
class Meta:
model = Foo
class BarForm(forms.ModelForm):
baz = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(),
queryset=Baz.objects.all())
class Meta:
model = Bar
exclude = ('foo',)
BarFormSet = inlineformset_factory(Foo, Bar, form=BarForm, can_delete=False)
Now, this works great in that I can render a single Foo and I get a number of inline forms for Bar. This renders is that the inline BarForm renders all the options of Baz as checkboxes.
What I would like is for each record of Baz to be rendered as a set of radio buttons representing the possible choices for value---along with a "N/A" choice---so that if A,B, or C is selected then the relationship to Baz is implied. But by default there doesn't seem to be a nice way of doing this with completely re-implementing RadioSelect or implementing a completely new widget, but I would like to follow the path of least resistance.
Hopefully I am making things clear.
My solution was to create SuperForm which allows subforms. The total solution is not quite generic for everyone but I did my best here to take what I was using and make it a bit more general.
Here is models.py:
from django.db import models
class Enemy(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Hero(models.Model):
name = models.CharField(max_length=200)
enemy = models.ManyToManyField(Enemy, through='Relationship', blank=False, null=False)
def __unicode__(self):
return self.name
class Relationship(models.Model):
hero = models.ForeignKey(Hero)
enemy = models.ForeignKey(Enemy)
ENEMY_TYPE_CHOICES = (
('G', 'Good'),
('B', 'Bad'),
('U', 'Ugly'),
)
enemy_type = models.CharField(max_length=1, choices=ENEMY_TYPE_CHOICES)
def __unicode__(self):
return u"{0} {1} {2}".format(self.hero, self.strength, self.relationship)
Here is what is in my forms.py:
from models import *
from django import forms
from django.forms.formsets import formset_factory, BaseFormSet
from django.forms.models import inlineformset_factory, BaseModelFormSet
from django.utils.safestring import mark_safe
class SuperForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.formsets = []
super(SuperForm, self).__init__(*args, **kwargs)
#property
def is_bound(self):
return self._is_bound or \
any([subformset.is_bound for subformset in self.formsets])
#is_bound.setter
def is_bound(self, value):
self._is_bound = value
def has_changed(self):
return bool(self.changed_data) or \
any([subformset.has_changed() for subformset in self.formsets])
def update_empty_permitted(self, subformset):
empty_permitted = not self.has_changed()
for subformset in self.formsets:
for form in subformset:
form.empty_permitted = empty_permitted
def is_valid(self):
subforms_valid = True
for subformset in self.formsets:
self.update_empty_permitted(subformset)
sfserrors = [err for err in subformset.errors if err]
if bool(sfserrors):
subforms_valid = False
return subforms_valid and super(SuperForm, self).is_valid()
class RelationshipForm(forms.ModelForm):
enemy = forms.ModelChoiceField(queryset=Enemy.objects.all(),
widget=forms.HiddenInput(),
required=True)
enemy_type = forms.ChoiceField(label="", widget=forms.RadioSelect,
choices=Relationship.ENEMY_TYPE_CHOICES,
required=True)
def name(self):
pk = self['enemy'].value()
return self.fields['enemy'].queryset.get(pk=pk)
class Meta:
model = Relationship
exclude = ('hero', 'enemy_type',)
RelationshipFormSet = formset_factory(RelationshipForm, extra=0, can_delete=False)
class HeroForm(SuperForm):
def __init__(self, *args, **kwargs):
super(HeroForm, self).__init__(*args, **kwargs)
initial=[dict(enemy=enemy.pk) for enemy in Enemy.objects.all()]
self.relationship_formset = RelationshipFormSet(initial=initial, prefix=self.prefix, data=kwargs.get('data'))
self.formsets.append(self.relationship_formset)
class Meta:
model = Hero
exclude = ('enemy',)
HeroFormSet = formset_factory(HeroForm, extra=1, can_delete=False)
Here is the views.py:
from django.views.generic import TemplateView
from django.views.generic.edit import FormMixin
from forms import *
class HeroView(FormMixin, TemplateView):
template_name = "formfun/hero.html"
form_class = HeroFormSet
def get_context_data(self, **kwargs):
context = super(HeroView, self).get_context_data(**kwargs)
context['formset'] = HeroFormSet()
return context
def form_valid(self, form):
return render_to_response({'form':form, 'valid': True})
def form_invalid(self, form):
return render_to_response({'form':form, 'valid': False})
And here is the template as I render it:
{% extends "base.html" %}
{% block title %}form fun{% endblock %}
{% block content %}
<form>
{% for form in formset %}
{{ form.as_p }}
{% for subform in form.relationship_formset %}
<label for="id_{{ subform.html_name }}_0" class="label50">{{ subform.name }}</label>
{{ subform.as_p }}
{% endfor %}
{% endfor %}
</form>
{% endblock content %}
Related
I am in a middle of a project. I have a model :-
class CustomersModels(models.Model):
def servChoices(servs):
lst = [x.serv_name for x in servs]
ch =()
for a in lst:
ch += (a,a),
return ch
customer_name = models.CharField(max_length=100)
comp_name = models.CharField(max_length=100)
ph_no = models.CharField(max_length=12)
webs_name = models.URLField(max_length=200)
service_insterested = models.OneToOneField(ServiceModel, on_delete = models.CASCADE)
def __str__(self):
return self.customer_name
I have a corresponding form for this model.
Now what i want is the fields customer_name, comp_name, webs_name
to be optional in one page. And required in another page.
Please guide me to establish the task in the most convenient manner
Deleted in 'def servChoices', service_insterested because I don't have access to them in the model. I also made it to return a string with the name of the class return 'CustomersModels', so that you can edit, delete records with empty values.
The field class 'customer_name', 'comp_name' ,'webs_name' are set to blank=True to make them optional.
In the NoForm, the required field is 'ph_no'. YesForm requires all fields to be filled in. For this, a validator (clean) is used, which will display a message on the page which field is not filled in (the form will not be sent until all fields are filled). You can read about clean here:
In the bboard views, replace with your folder where you have the templates (this is the string template_name = 'bboard/templ_yes.html').
urls.py
urlpatterns = [
path('yes/', YesCreateView.as_view(), name='yes'),
path('no/', NoCreateView.as_view(), name='no'),
]
views.py
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .forms import NoForm, YesForm
class NoCreateView(CreateView):
template_name = 'bboard/templ_no.html'
form_class = NoForm
success_url = reverse_lazy('no')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
class YesCreateView(CreateView):
template_name = 'bboard/templ_yes.html'
form_class = YesForm
success_url = reverse_lazy('yes')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
forms.py
from django.forms import ModelForm
from .models import CustomersModels
from django.core.exceptions import ValidationError
class YesForm(ModelForm):
class Meta:
model = CustomersModels
fields = ('customer_name', 'comp_name', 'ph_no', 'webs_name')
def clean(self):
cleaned_data = super().clean()
customer_name = cleaned_data.get('customer_name')
comp_name = cleaned_data.get('comp_name')
webs_name = cleaned_data.get('webs_name')
if len(customer_name) <= 0 or customer_name == '':
raise ValidationError(
"fill in the field customer_name"
)
if len(comp_name) <= 0 or comp_name == '':
raise ValidationError(
"fill in the field comp_name"
)
if len(webs_name) <= 0 or webs_name == '':
raise ValidationError(
"fill in the field webs_name"
)
class NoForm(ModelForm):
class Meta:
model = CustomersModels
fields = ('customer_name', 'comp_name', 'ph_no', 'webs_name')
models.py
class CustomersModels(models.Model):
customer_name = models.CharField(max_length=100, blank=True)
comp_name = models.CharField(max_length=100, blank=True)
ph_no = models.CharField(max_length=12)
webs_name = models.URLField(max_length=200, blank=True)
def __str__(self):
return 'CustomersModels'
tepmplate(templ_yes.html)
<h2>form</h2>
<form method="post" action="{% url 'yes'%}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="adding">
</form>
tepmplate(templ_no.html)
<h2>form</h2>
<form method="post" action="{% url 'no'%}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="adding">
</form>
I posted a screenshot below to hopefully make this easier to understand so I will reference it in my question.
I am trying to create a recipe cost calculator app. I have 3 model namely Recipe, RecipeIngredient and Ingredient
The user will add ingredients to the database and then create recipes using those ingredients throught the RecipeIngredient model.
When creating an Ingredient the user selects a unit eg. (grams). Now when the user goes to create a Recipe (see screenshot below) I want to only display the units that are relevant to that ingredient and not all of them for eg. A user added Beef and the unit was grams now when the user is adding a RecipeIngredient to the Recipe I want them to only be able to select from the "Weight" category in the example (see screenshot below). The reason for this is because grams can't be converted to milliliters and so it shouldn't even be a choice.
If anything is unclear or you need more information just let me know.
Models
Recipe Models
from django.db import models
from django.urls import reverse
from django.contrib.auth import get_user_model
from ingredients.models import Ingredient
class Recipe(models.Model):
"""Store recipe information and costs"""
class Meta:
"""Meta definition for Recipe."""
verbose_name = "Recipe"
verbose_name_plural = "Recipes"
ordering = ("name",)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=155)
yield_count = models.PositiveIntegerField()
yield_units = models.CharField(max_length=155, default="servings")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("recipe:detail", kwargs={"id": self.pk})
def get_update_url(self):
return reverse("recipe:update", kwargs={"id": self.pk})
class RecipeIngredient(models.Model):
"""Holds more information about how much of a certain ingredient is used inside a recipe"""
class Meta:
"""Meta definition for RecipeIngredient"""
verbose_name = "RecipeIngredient"
verbose_name_plural = "RecipeIngredients"
# NOTE: User should first choose the ingredient
recipe = models.ForeignKey(Recipe, related_name="ingredients", on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, related_name="ingredient", on_delete=models.CASCADE)
# NOTE: Then the amount an unit should be asked based on that
amount = models.DecimalField(max_digits=20, decimal_places=2)
unit = models.CharField(max_length=10, choices=Ingredient.UNIT_CHOICES, default=None)
def __str__(self):
"""Unicode representation of RecipeIngredient"""
return self.ingredient.__str__()
Ingredient Model
from django.db import models
from django.urls import reverse
from django.contrib.auth import get_user_model
from pint import UnitRegistry
from .choices import ALL_UNIT_CHOICES
class Ingredient(models.Model):
"""Stores data about an ingredient to be used by a Recipe model."""
class Meta:
"""Meta definition for Ingredient."""
verbose_name = "Ingredient"
verbose_name_plural = "Ingredients"
ordering = ("name",)
# Unit choices that will display in a dropdown
# It will be ordered by group (eg. Weight, Volume)
UNIT_CHOICES = ALL_UNIT_CHOICES
ureg = UnitRegistry()
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=50, unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
size = models.DecimalField(max_digits=10, decimal_places=2)
unit = models.CharField(max_length=10, choices=UNIT_CHOICES, default=None)
def __str__(self):
"""Unicode representation of Ingredient."""
return self.name
def get_absolute_url(self):
"""Return absolute url for Ingredient"""
return reverse("ingredient:detail", kwargs={"id": self.pk})
def get_update_url(self):
"""Return the url for update page"""
return reverse("ingredient:update", kwargs={"id": self.pk})
def get_delete_url(self):
return reverse("ingredient:delete", kwargs={"id": self.pk})
def save(self, *args, **kwargs):
self.unit = self.ureg.Unit(self.unit)
super(Ingredient, self).save(*args, **kwargs)
Views
def create_recipe(request):
template_name = "recipes/create.html"
form = CreateRecipeForm(request.POST or None)
ingredient_choice_form = IngredientChoiceForm(request.POST or None, request=request)
Formset = modelformset_factory(RecipeIngredient, form=CreateRecipeIngredientForm, extra=0)
formset = Formset(request.POST or None, queryset=Recipe.objects.none())
context = {
"form": form,
"ingredient_choice_form": ingredient_choice_form,
"formset": formset,
}
print(request.POST)
return render(request, template_name, context)
# This is the view being calle by HTMX everytime an ingredient is selected
def get_recipe_ingredient_details(request):
template_name = "recipes/hx_snippets/add_recipe_ingredient_details.html"
ingredient_id = request.GET.get("ingredients")
if not ingredient_id:
return HttpResponse("")
ingredient = Ingredient.objects.get(id=ingredient_id)
choices = get_unit_group(ingredient.unit)
Formset = modelformset_factory(RecipeIngredient, form=CreateRecipeIngredientForm, extra=1)
formset = Formset(
request.POST or None,
queryset=RecipeIngredient.objects.none(),
initial=[
{
"ingredient": ingredient,
"unit": choices,
},
],
)
context = {
"formset": formset,
"ingredient": ingredient,
}
Templates
Template returned by HTMX request
<p>
{% for form in formset %}
{{ form }}
{% endfor %}
</p>
main create template
{% block content %}
<h1>Create Recipe</h1>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<h3>Add ingredients</h3>
{{ formset.management_form }}
<div id="ingredient-form"></div>
{{ ingredient_choice_form.as_p }} <-- this is an HTMX get request that calls the get_recipe_ingredient_details view
<p><button type="submit">Create</button></p>
</form>
{% endblock content %}
{% block scripts %}
<script src="{% static 'js/recipes/addFormsetField.js' %}"></script>
{% endblock scripts %}
Choices.py
from django.db import models
class WeightUnitChoices(models.TextChoices):
GRAMS = "gram", "gram"
OZ = "ounce", "ounce"
LBS = "pound", "pound"
KG = "kilogram", "kilogram"
T = "tonne", "tonne"
class VolumeUnitChoices(models.TextChoices):
ML = "milliliter", "milliliter"
L = "liter", "liter"
TSP = "teaspoon", "teaspoon"
TBPS = "tablespoon", "tablespoon"
ALL_UNIT_CHOICES = [
(None, "Select Unit"),
("Weight", WeightUnitChoices.choices),
("Volume", VolumeUnitChoices.choices),
]
def get_unit_group(unit):
"""returns the unit group to which unit belongs to"""
match unit:
case unit if unit in dict(WeightUnitChoices.choices):
return WeightUnitChoices.choices
case unit if unit in dict(VolumeUnitChoices.choices):
return VolumeUnitChoices.choices
case _:
# Return all available units
return ALL_UNIT_CHOICES
Screenshot
So i have a model called folder, i want to show all the folder created by current user on HomeView, but somehow it's not working, what i think is that folders are not getting connected to user who created them.
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.deletion import CASCADE
from django.core.validators import MinValueValidator
from django.core.exceptions import PermissionDenied
# The Folders Model.
class Folder(models.Model):
name = models.CharField(max_length = 250)
parent = models.ForeignKey('self', on_delete = CASCADE, null = True, blank = True )
cr_date = models.DateTimeField(auto_now_add = True)
user = models.OneToOneField(to=User, on_delete=CASCADE, null=True, blank=True)
def __str__(self):
return "%s" % self.name
view.py
class HomeView(TemplateView):
template_name = "ahmed_drive/home.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
home_folders = Folder.objects.filter(user=user).order_by('-cr_date')
home_files = Fileshare.objects.filter(uploaded_by=user).order_by('-id')
context['home_folders'] = home_folders
context['home_files'] = home_files
return context
#method_decorator(login_required, name="dispatch")
class FolderCreate(CreateView):
model = Folder
fields = ["name", "parent"]
def formvalid():
if form.is_valid():
form.save()
return redirect('home.html')
else :
return render(request,{'form': form})
home.html
{% extends 'base.html' %}
{% block content %}
{% for Folder in home_folders %}
<h1> {{Folder.name}} </h1>
{% endfor %}
{% endblock %}
when you create folder you don't connect user to it.
you need to do it before saving form
def form_valid(self, form):
folder = form.save(commit=False)
folder.user = request.user
folder.save()
return redirect('home.html')
My project has many physics questions stored in its database where each of these questions belongs to a Physics' topic and a Question type.
I have two ChoiceField:
* One for topics and includes 16 topics.
* One for question type and includes two question types.
I have a submit button that is supposed to show me the results of my filtering, however, I don't know how to write the Query Sets in the views.py although I have read the Documentation but still don't know how to make one query or more to get my results.
This is the models.py
from django.db import models
from home.choices import *
# Create your models here.
class Topic(models.Model):
topic_name = models.IntegerField(
choices = question_topic_name_choices, default = 1)
def __str__(self):
return '%s' % self.topic_name
class Image (models.Model):
image_file = models.ImageField()
def __str__(self):
return '%s' % self.image_file
class Question(models.Model):
question_type = models. IntegerField(
choices = questions_type_choices, default = 1)
question_topic = models.ForeignKey( 'Topic',
on_delete=models.CASCADE,
blank=True,
null=True)
question_description = models.TextField()
question_answer = models.ForeignKey( 'Answer',
on_delete=models.CASCADE,
blank=True,
null=True)
question_image = models.ForeignKey( 'Image',
on_delete=models.CASCADE,
blank=True,
null=True)
def __str__(self):
return '%s' % self.question_type
class Answer(models.Model):
answer_description = models.TextField()
answer_image = models.ForeignKey( 'Image',
on_delete=models.CASCADE,
blank=True,
null=True)
def __str__(self):
return '%s' % self.answer_description
This is the forms.py
from django import forms
from betterforms.multiform import MultiModelForm
from .models import Topic, Image, Question, Answer
from .choices import questions_type_choices, question_topic_name_choices
class TopicForm(forms.ModelForm):
topic_name = forms.ChoiceField(
choices=question_topic_name_choices,
widget = forms.Select(
attrs = {'class': 'home-select-one'}
))
class Meta:
model = Topic
fields = ['topic_name',]
def __str__(self):
return self.fields
class QuestionForm(forms.ModelForm):
question_type = forms.ChoiceField(
choices= questions_type_choices,
widget = forms.Select(
attrs = {'class': 'home-select-two'},
))
class Meta:
model = Question
fields = ['question_type',]
def __str__(self):
return self.fields
class QuizMultiForm(MultiModelForm):
form_classes = {
'topics':TopicForm,
'questions':QuestionForm
}
This is the views.py
from django.shortcuts import render, render_to_response
from django.views.generic import CreateView, TemplateView
from home.models import Topic, Image, Question, Answer
from home.forms import QuizMultiForm
def QuizView(request):
if request.method == "POST":
form = QuizMultiForm(request.POST)
if form.is_valid():
pass
else:
form = QuizMultiForm()
return render(request, "index.html", {'form': form})
This is the index.html
{% extends 'base.html' %} {% block content %}
<form method="POST">
{% csrf_token %} {{ form.as_p }}
<button type="submit" id="home-Physics-time-button">
It is Physics Time</button>
</form>
{% endblock content %}
Any help would be great. Thank you!
i dont know what exactly you want to filter or if i understood correctly (cant add comments yet), but here is an example:
views.py
def QuizView(request):
topics = Topic.objects.filter(topic_name=1) # i dont know your choices, but i go with the set default
if request.method == "POST":
form = QuizMultiForm(request.POST)
if form.is_valid():
pass
else:
form = QuizMultiForm()
return render(request, "index.html", {'form': form, 'topics':'topics})
template part for calling now the query
{% for topic in topics %}
<h1> {{ topic.topic_name }} </h1>
{% endfor %}
explanation: you are filtering the query in your view by .filter(model_field=)
in your template you iterate trough all results (you are passing 'topics' from the view into the template by the context parameter in your curly brackets), filtered by the view
I want to retrieve the question_description answer_descritption and question_image answer_image if found in the database according to topic and question type using two ChoiceField for both: Topic and Question Type.
However, I don't know how to do that. I have seen some tutorials and gotten glimpses of what I have to do, but I am not sure how to preform the same techniques on my case because online there are not that many ChoiceField examples, except that there are general examples on how to use forms and extract data from the database.
This is the models.py
from django.db import models
from home.choices import *
# Create your models here.
class Topic(models.Model):
topic_name = models.IntegerField(
choices = question_topic_name_choices, default = 1)
def __str__(self):
return '%s' % self.topic_name
class Image (models.Model):
image_file = models.ImageField()
def __str__(self):
return '%s' % self.image_file
class Question(models.Model):
question_type = models. IntegerField(
choices = questions_type_choices, default = 1)
question_topic = models.ForeignKey( 'Topic',
on_delete=models.CASCADE,
blank=True,
null=True)
question_description = models.TextField()
question_answer = models.ForeignKey( 'Answer',
on_delete=models.CASCADE,
blank=True,
null=True)
question_image = models.ForeignKey( 'Image',
on_delete=models.CASCADE,
blank=True,
null=True)
def __str__(self):
return '%s' % self.question_type
class Answer(models.Model):
answer_description = models.TextField()
answer_image = models.ForeignKey( 'Image',
on_delete=models.CASCADE,
blank=True,
null=True)
answer_topic = models.ForeignKey( 'Topic',
on_delete=models.CASCADE,
blank=True,
null=True)
def __str__(self):
return '%s' % self.answer_description
This is the forms.py
from django import forms
from betterforms.multiform import MultiModelForm
from .models import Topic, Image, Question, Answer
from .choices import questions_type_choices, question_topic_name_choices
class TopicForm(forms.ModelForm):
topic_name = forms.ChoiceField(
choices=question_topic_name_choices,
widget = forms.Select(
attrs = {'class': 'home-select-one'}
))
class Meta:
model = Topic
fields = ['topic_name',]
def __str__(self):
return self.fields
class QuestionForm(forms.ModelForm):
question_type = forms.ChoiceField(
choices= questions_type_choices,
widget = forms.Select(
attrs = {'class': 'home-select-two'},
))
class Meta:
model = Question
fields = ['question_type',]
def __str__(self):
return self.fields
class QuizMultiForm(MultiModelForm):
form_classes = {
'topics':TopicForm,
'questions':QuestionForm
}
This is the views.py
from django.shortcuts import render, render_to_response
from django.views.generic import TemplateView
from home.models import Topic, Image, Question, Answer
from home.forms import QuizMultiForm
class QuizView(TemplateView):
template_name = 'index.html'
def get(self, request):
# What queries do I need to put here to get the question and answer's description according to the ChoiceField input
form = QuizMultiForm()
return render (request, self.template_name, {'form': form})
def post(self, request):
form = QuizMultiForm(request.POST)
if form.is_valid():
text = form.cleaned_data['topic_name', 'question_type'] # I don't know what to put here!
args = {'form': form, 'text': text}
return render (request, self.template_name, args)
This is the template:
{% extends 'base.html' %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" id="home-Physics-time-button">It is Physics Time</button>
<h1> {{ text }} </h1>
</form>
{% endblock content %}
I would appropriate the help!
Thank you!
The cleaned_data attribute of the form, contains a dictionary that maps the name of the field with the bounded data. And from the MultiForm docs you can read:
cleaned_data
Returns an OrderedDict of the cleaned_data for each of the child forms.
Just extract data like this:
topic_name = form.cleaned_data['topics']['topic_name']
question_type = form.cleaned_data['question']['question_type']
# And so on ...