Django ManyToManyField Persistence Fails - django

I have a simple Django 3.1.0 app I need to create in order to assign Tasks with Tags (or assign tags into tasks).
My Model
class Task(models.Model):
user = models.CharField(max_length=33)
time = models.DateTimeField(auto_now_add=True)
task = models.CharField(max_length=500)
tags = models.ForeignKey('Tag', on_delete=models.SET_NULL, null=True)
class Tag(models.Model):
tag = models.CharField(max_length=30, default="No Tag")
members = models.ManyToManyField('Task')
class Meta:
verbose_name = "tag"
verbose_name_plural = "tags"
My Form
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ['user', 'task', 'tags']
template_name = 'tasks.html'
tags = ModelMultipleChoiceField(
queryset=Tag.objects.values().all(), widget=CheckboxSelectMultiple()
)
My View
def main(request):
model = Task.objects.values().all()
form = TaskForm()
con = {'context': list(model), 'form': form}
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
form.save_m2m()
return redirect('/')
else:
form = TaskForm()
return render(request, "tasks.html", con)
The migrations are successfull, and with the above code, the view shows a checkbox list with the fetched tags, but the problem is that when I hit Submit on the form, the values are not saved/written on the database but the page reloads successfully.
However, if I turn the following:
obj = form.save(commit=False)
form.save_m2m()
into
form.save(commit=True)
#form.save_m2m()
the values are written only from the fields 'user', 'task' - without the 'tags'
It's also funny that what fetches back on the webpage as values of the tags is in the shape of:
[checkbox] {'id': 1, 'tag': 'aks'}
What am I doing wrong? Thanks.
UPDATE after a comment below:
As Abdul Aziz suggested, I had to remove the values() from the queryset. But after that , to make it work, I had to add also:
In the model:
tag = models.CharField(max_length=100, default="No Tags")
and then refer to that one in the form and Vue template.

You have a ForeignKey set to the Tag model on your Task model, when you actually want a ManyToMany relationship between them. Remove the foreign key and set a related_name to the ManyToManyField in the Tag model like so:
class Task(models.Model):
user = models.CharField(max_length=33)
time = models.DateTimeField(auto_now_add=True)
task = models.CharField(max_length=500)
class Tag(models.Model):
tag = models.CharField(max_length=30, default="No Tag")
members = models.ManyToManyField('Task', related_name="tags")
class Meta:
verbose_name = "tag"
verbose_name_plural = "tags"
Also in your form you have:
tags = ModelMultipleChoiceField(
queryset=Tag.objects.values().all(), widget=CheckboxSelectMultiple()
)
Why are you using values here? Remove it:
tags = ModelMultipleChoiceField(
queryset=Tag.objects.all(), widget=CheckboxSelectMultiple()
)

Related

How to filter data dynamically by supply values from form using django

I want to filter Blog Post objects or records based on the Post Category and a User that uploaded the Post record, it gives me an error when I try to do filter, this is the error.
ValueError at /dashboard/filter-post/
The QuerySet value for an exact lookup must be limited to one result using slicing.
Here is my models.py
class Category(models.Model):
cat_name = models.CharField(max_length=100, verbose_name='Category Name')
cat_desc = models.TextField(blank=True, null=True)
def __str__(self):
return self.cat_name
class Meta():
verbose_name_plural='Category'
class Post(models.Model):
pst_title = models.CharField(max_length=150)
pst_image = models.ImageField(blank=True, null=True, upload_to='uploads/')
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.pst_title
#property
def img_url(self):
if self.pst_image:
return self.pst_image.url
on forms.py
class FilterForm(forms.ModelForm):
user = forms.ModelChoiceField(
queryset=User.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}))
category = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
widget=forms.SelectMultiple(attrs={'class': 'form-control js-example-disabled-results'}))
catch_bot = forms.CharField(required=False,
widget=forms.HiddenInput, validators=[validators.MaxLengthValidator(0)])
class Meta():
fields = ['user', 'category' ]
model = Post
on views.py
def filter_post(request):
post = FilterForm(request.GET)
queryset = Post.objects.all()
if post.is_valid():
user=post.cleaned_data.get('user')
category=post.cleaned_data.get('category')
if user and category:
queryset = queryset.filter(user__username=user, category__cat_name=category)
return render(request, 'backend/filter-post.html', {'query':queryset, 'post':post})
I am having challenges properly filtering this in my views any help?
Try this:
instead of this:
queryset = queryset.filter(user__username=user, category__cat_name=category)
use this:
queryset = queryset.filter(user=user, category=category)
Also don't name your model fields after the model name, just use name instead of pst_name or cat_name, you will see that when you will try access these values there will be no confusion.
UPDATE
Ok, maybe try to rewrite your view like this:
def filter_post(request):
posts = Post.objects.all()
form = FilterForm(request.GET) # its best practice to call your form instance `form` in the view so that the next line has better readability
if form.is_valid():
user=post.cleaned_data['user']
category=post.cleaned_data['category']
if user:
posts = posts.filter(user=user)
if category:
posts = posts.filter(category=category)
return render(request, 'backend/filter-post.html', {'posts':posts})

Django Unique Constraint doesn't Work on creating unique project titles for each user

So I have this project where I have several supervisors that can create projects. I want that for each supervisor they can't make a project with the same title. I tried to use UniqueConstraint but now it's not working. Supervisors can still create a project with the same title. Note: Project's supervisor is automatically assigned to the project creator.
models.py
class Project(models.Model):
title = models.CharField(max_length=100)
due_date = models.DateField()
due_time = models.TimeField()
supervisor = models.ForeignKey(User, default=None, on_delete=models.SET_DEFAULT)
class Meta:
constraints = [models.UniqueConstraint(fields=['title', 'supervisor'], name="unique title")]
verbose_name = "Project"
def __str__(self):
return str(self.title) + "-" + str(self.supervisor)
forms.py
class CreateProjects(forms.ModelForm):
class Meta:
model = models.Project
fields = ['title', 'due_date', 'due_time']
widgets = {
'due_date': DateInput()
}
views.py
#login_required(login_url="/signin")
def create_project(response):
if response.user.is_staff:
if response.method == 'POST':
form = forms.CreateProjects(response.POST, response.FILES)
if form.is_valid():
# save project to db
instance = form.save(commit=False)
instance.supervisor = response.user
print(instance.supervisor)
instance.save()
return redirect('/dashboard')
else:
form = forms.CreateProjects(initial={'supervisor': response.user})
ctx = {'form': form, 'FullName': response.user.get_full_name}
else:
form = "Only Teachers can create projects"
ctx = {'form': form, 'FullName': response.user.get_full_name}
return render(response, "create_project.html", ctx)
The simplest way is to define unique on the field.
class Project(models.Model):
title = models.CharField(max_length=100,unique=True)
repeat on any field(s) you want it to be unique.

Django ModelChoiceField Issue

I've got the following Situation, I have a rather large legacy model (which works nonetheless well) and need one of its fields as a distinct dropdown for one of my forms:
Legacy Table:
class SummaryView(models.Model):
...
Period = models.CharField(db_column='Period', max_length=10, blank=True, null=True)
...
def __str__(self):
return self.Period
class Meta:
managed = False # Created from a view. Don't remove.
db_table = 'MC_AUT_SummaryView'
Internal Model:
class BillCycle(models.Model):
...
Name = models.CharField(max_length=100, verbose_name='Name')
Period = models.CharField(max_length=10, null=True, blank=True)
Version = models.FloatField(verbose_name='Version', default=1.0)
Type = models.CharField(max_length=100, verbose_name='Type', choices=billcycle_type_choices)
Association = models.ForeignKey(BillCycleAssociation, on_delete=models.DO_NOTHING)
...
def __str__(self):
return self.Name
Since I don't want to connect them via a Foreign Key (as the SummaryView is not managed by Django) I tried a solution which I already used quite a few times. In my forms I create a ModelChoiceField which points to my Legacy Model:
class BillcycleModelForm(forms.ModelForm):
period_tmp = forms.ModelChoiceField(queryset=SummaryView.objects.values_list('Period', flat=True).distinct(),
required=False, label='Period')
....
class Meta:
model = BillCycle
fields = ['Name', 'Type', 'Association', 'period_tmp']
And in my view I try to over-write the Period Field from my internal Model with users form input:
def billcycle_create(request, template_name='XXX'):
form = BillcycleModelForm(request.POST or None)
data = request.POST.copy()
username = request.user
print("Data:")
print(data)
if form.is_valid():
initial_obj = form.save(commit=False)
initial_obj.ModifiedBy = username
initial_obj.Period = form.cleaned_data['period_tmp']
initial_obj.Status = 'Creating...'
print("initial object:")
print(initial_obj)
form.save()
....
So far so good:
Drop Down is rendered correctly
In my print Statement in the View ("data") I see that the desired infos are there:
'Type': ['Create/Delta'], 'Association': ['CP'], 'period_tmp': ['2019-12']
Still I get a Select a valid choice. That choice is not one of the available choices. Error in the forms. Any ideas??

Django Hidden Foreign Key in form using Meta model

I am working on a cookbook website using django and have run into a problem with a foreign key field in my form
the problem is that when i create my recipe i need to have a foreign key pointing to the cookbook that created this recipe but I don't want the user creating the recipe to see the original_cookbook field (they shouldn't have to)
I believe I need to use a widget (HiddenInput) but am I getting confused with the examples given on other sites. Also my friend mentioned something about setting an initial value in the original_cookbook view
tl;dr: I want to point the fk to the users cookbook while keeping the original_cookbook field hidden.
relevant code:
form:
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
model:
class Recipe(models.Model):
def __unicode__(self):
return self.name
original_cookbook = models.ForeignKey(Cookbook)
#cookbooks = models.ManyToManyField('Cookbook', related_name = 'recipes')
name = models.CharField(max_length=200)
author = models.CharField(max_length= 100)
picture = models.ImageField(upload_to = 'Downloads', blank=True)
pub_date = models.DateTimeField('date published', auto_now_add=True, blank=True)
ingredients = models.TextField()
steps = models.TextField()
prep_time = models.IntegerField()
TYPE_CHOICES= (
('SW', 'Sandwich'),
('AP', 'Appetizers'),
('SD', 'Sauces and Dressings'),
('SS', 'Soups and Salads'),
('VG', 'Vegetables'),
('RG', 'Rice, Grains and Beans'),
('PA', 'Pasta'),
('BR', 'Breakfast'),
('MT', 'Meat'),
('SF', 'Seafood'),
('BP', 'Bread and Pizza'),
('DT', 'Desserts'),
)
type = models.CharField(max_length = 2, choices=TYPE_CHOICES)
def index_queryset(self):
return self.objects.all()
view:
def createrecipe(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/index/')
else:
if request.method == 'POST':
form = RecipeForm(request.POST)
if form.is_valid():
recipe = form.save()
user = request.user
cookbooks = user.cookbooks
cookbook = cookbooks.all()[0]
cookbook.recipes.add(recipe)
return HttpResponseRedirect('/account')
else:
form = RecipeForm()
return render_to_response('cookbook/createrecipe.html',
{'form':form},
context_instance=RequestContext(request))
Add exclude = ('original_cookbook',) to your form's Meta class.
Then, in your if form.is_valid() code, do something like:
....
recipe = form.save(commit=False)
recipe.original_cookbook = whatever_that_is
recipe.save()
...
This is answered in the documentation.
from django import forms
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
widgets = {'cookbook': forms.HiddenInput

Validation fails on a select box whose contents are added by an Ajax call

This question is related to this one
Remove all the elements in a foreign key select field
I had a foreign key field in my model which was getting pre-populated by its data and I wanted the select list to be empty. I did achieve that but the validation fails when I submit the form.
The error says "Select a valid choice option. 1 is not one of the available choices).
These are my models
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
serving_size = models.ForeignKey(ServingSize)
quantity = models.IntegerField()
order = models.IntegerField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
class RecipeIngredientForm(forms.ModelForm):
serving_size = forms.ChoiceField(widget=forms.Select())
class Meta:
serving_size = forms.ChoiceField(widget=forms.Select())
model = RecipeIngredient
fields = ('ingredient', 'quantity', 'serving_size')
widgets = {
'ingredient': forms.TextInput(attrs={'class' : 'recipe_ingredient'}),
'quantity': forms.TextInput(),
'serving_size' : forms.Select(attrs={'class' : 'ddl'}),
}
I get an error on the third line
recipeIngredients = models.RecipeIngredientFormSet(request.POST)
print(recipeIngredients.errors)
objRecipeIngredients = recipeIngredients.save(commit=False)
I want the select box to be empty because it gets populated by an ajax call. Any ideas what to do so the model passes the validation?
EDIT
Serving Size Model
class ServingSize(models.Model):
name = models.CharField(max_length = 255)
unit = models.CharField(max_length = 125)
food_group = models.ForeignKey(FoodGroup)
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
objects = models.Manager()
dal_objects = ServingSizeManager()
def __unicode__(self):
return self.name;
First, why do you have serving_size in the Meta class?
I would use an extra field in the ModelForm and leave out serving_size field altogether.
class RecipeIngredientForm(forms.ModelForm):
mycustomfield = forms.ChoiceField(widget=forms.Select())
class Meta:
model = RecipeIngredient
exclude = ('serving_size', 'created', 'updated') #etc
Then in the view I would manipulate the form to assign a valid ServingSize to the serving_size field.
[EDIT]
Alright, your actual implementation will depend on what you are pulling through ajax and how. But see the following code: -
Your form: -
class CustomRecipeIngredientForm(forms.ModelForm):
recipe = forms.ModelChoiceField( Recipe.objects.all(),
widget=forms.Select(attrs={'class':'customclass',}))
ingredient = forms.ModelChoiceField( Ingredient.objects.all(),
widget=forms.Select(attrs={'class':'recipe_ingredient',}))
my_custom_serving_size_field = forms.ChoiceField(widget=forms.Select(attrs={'class':'ddl',}))
quantity = forms.IntegerField()
order = forms.IntegerField()
class Meta:
model = RecipeIngredient
exclude = ('serving_size', 'created', 'updated',)
Pull your data through ajax into the my_custom_serving_size_field
Your view: -
def my_view(request):
if request.method == 'POST':
form = CustomRecipeIngredientForm(data=request.POST)
if form.is_valid():
new_recipe_ingredient = form.save(commit=False)
new_recipe_ingredient.serving_size = ServingSize.objects.get(pk=form.cleaned_data['my_custom_serving_size_field'])
new_recipe_ingredient.save()
return HttpResponseRedirect(reverse('redirect_to_wherever'))
else:
form = CustomRecipeIngredientForm()
return render_to_response('path/to/my_template.html', {'form': form}, context_instance=RequestContext(request))
Of course, your ServingSize.objects.get() logic will depend on what your are pulling through ajax and how. Try something along these lines and let us know.
Looks like you want a ModelChoiceField, which
Allows the selection of a single model
object, suitable for representing a
foreign key