ManyToMany Relationship between two models in Django - python

I am trying to build a website that users can add the courses they are taking. I want to know how should I add the ManyToMany relationship. Such that we can get all users in a course based on the course code or instructor or any field. And we can also get the courses user is enrolled in. Currently, my Database structure is:
class Course(models.Model):
course_code = models.CharField(max_length=20)
course_university = models.CharField(max_length=100)
course_instructor = models.CharField(max_length=100)
course_year = models.IntegerField(('year'), validators=[MinValueValidator(1984), max_value_current_year])
def __str__(self):
return self.course_code
and my user model:
class Profile(AbstractUser):
bio = models.TextField()
image = models.ImageField(default='defaults/user/default_u_i.png',
courses = models.ManyToManyField('home.Course',related_name='courses')
def __str__(self):
return self.username
I was wondering should ManyToMany relationship be in User model or the course model? Or will it make any difference at all?
EDIT: For adding course to post object now I am using this view but it seems to not work:
#login_required
def course_add(request):
if request.method == "POST":
form = CourseForm(request.POST or none)
if form.is_valid():
course = form.save()
request.user.add(course)
else:
form = CourseForm
context = {
'form':form
}
return render(request,'home/courses/course_add.html', context)

For a relational databases, the model where you define the ManyToManyField does not matter. Django will create an extra table with two ForeignKeys to the two models that are linked by the ManyToManyField.
The related managers that are added, etc. is all Django logic. Behind the curtains, it will query the table in the middle.
You however need to fix the related_name=… parameter [Django-doc]. The related_name specifies the name of the relation in reverse so from Course to Profile in this case. It thus should be something like 'profiles':
class Profile(AbstractUser):
bio = models.TextField()
image = models.ImageField(default='defaults/user/default_u_i.png',
courses = models.ManyToManyField('home.Course', related_name='profiles')
def __str__(self):
return self.username
You thus can obtain the people that particiate in a Course object with:
mycourse.profiles.all()
and you can access the courses in which a Profile is enrolled with:
myprofile.courses.all()
For more information, see the Many-to-many relationships section of the documentation.
You can add a course to the courses of a user with:
#login_required
def course_add(request):
if request.method == 'POST':
form = CourseForm(request.POST)
if form.is_valid():
course = form.save()
request.user.courses.add(course)
else:
form = CourseForm()
context = {
'form': form
}
return render(request,'home/courses/course_add.html', context)

You don't need to add the related name. Default is "courses_set" in your case.
Here is excerpt from: https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects
Following relationships “backward” If a model has a ForeignKey,
instances of the foreign-key model will have access to a Manager that
returns all instances of the first model. By default, this Manager is
named FOO_set, where FOO is the source model name, lowercased. This
Manager returns QuerySets, which can be filtered and manipulated as
described in the “Retrieving objects” section above.

Related

Django Model: How to only access fields related to a specific user from another model

How can i only access the addresses(Address model) of specified user(User model) from Order model.
here is the code: Models.py
class User(AbstractBaseUser, PermissionsMixin):
phone_number = PhoneField(max_length=12, primary_key=True, unique=True)
class Address(models.Model):
address = models.CharField(max_length=500, blank=False,null=False,primary_key=True)
customer = models.ForeignKey((User, on_delete= models.CASCADE)
class Order(models.Model):
order = CharField(max_length=400,blank=False,null=False)
customer = models.ForeignKey(User,on_delete=models.SET_NULL, null=True)
address = models.ForeignKey(Address,on_delete=models.SET_NULL, null=True)
the address field in Order model is my problem. When creating a new order in Django Administration, when i select one of the customers, i still can choose any address registered in database
How can i limit the access to addresses to specified user.
Thanks in advance
You can not filter this in the models. You will need to do that by the form layer.
We can implement this with:
class MyForm(forms.ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
self.fields['address'].queryset = Address.objects.filter(user=user)
class Meta:
model = Order
fields = ['address']
then in the view, we can construct a form with the logged in user as user:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
if request.method == 'POST':
form = MyForm(request.POST, user=request.user)
if form.is_valid():
form.instance.user = request.user
# set the order number to the instance
form.save()
return redirect('name-of-some-view')
else:
form = MyForm(user=request.user)
return render(request, 'name-of-some-template.html', {'form': form})
It depends if customers should be allowed to have multiple addresses (like in most online shops). In that case the User and Address models look pretty good!
Django Admin can be a bit tricky. But that's just the nature of the flow, because the moment you open the "Create Order Page" the server has no idea what user you will pick, and therefore does not know which address it should filter. You would have to use ajax to get to your goal, but I can propose something different...
The question is why did you add another address field to the Order? Don't get me wrong, it's the right way actually. But...
What if the user orders something, changes his address object and looks back at the order history?
Actually you COULD drop the address-foreignkey on the order and you'll still be able to access the current customer address on any order, by:
some_order = Order.objects.first()
customer = some_order.customer
# As defined in your model, one customer can have many addresses. For now just access the "latest" one
customers_address = customer.address_set.last()
But the order history would still be messy... now its even worse. Whenever the customer adds or changes the address, the order history would show wrong values.
To prevent this, you could leave the foreign key, prevent the address_id from being edited (read_only field), prevent the related address object from being edited, and add a flag if the address is visible to the user or soft-deleted.
You should do some research about read_only fields, editable and overriding model methods
But to keep things a bit more simple, lets just change the Order->address field to be a Charfield instead of a foreign key. You won't need to show an editable field inside the admin anymore and instead let the user have his default address.
class Order(models.Model):
order = CharField(max_length=400,blank=False,null=False)
customer = models.ForeignKey(User,on_delete=models.SET_NULL, null=True)
shipping_address = models.CharField(max_length=500, editable=False)
def save(self, *args, **kwargs):
# You can use this field to stringify even more complex objects
# Again, last() is not the right way in the end but you could have a specific field on the customer: preferred_address
self.shipping_address = self.customer.address_set.last().address
super().save(*args, **kwargs)

Trying to set user field in the nested form of a django nested inline formset - fails

I followed this: https://www.yergler.net/2009/09/27/nested-formsets-with-django/ and this: django inline formsets with a complex model for the nested form and overall my code works great.
class Account(models.Model):
user_username = models.ForeignKey(User, on_delete=models.CASCADE)
account_name = models.CharField(max_length=30)
class Classification(models.Model):
user_username=models.ForeignKey(User, on_delete=models.CASCADE)
data_id=models.ForeignKey(ImportData, on_delete=models.CASCADE)
class ImportData(models.Model):
user_username = models.ForeignKey(User, on_delete=models.CASCADE)
data_id = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False)
ClassificationFormset = inlineformset_factory(ImportData, Classification, exclude=('user_username',), extra=1)
# below is just what came from the nested formset links above: pasted here for easy reference.
class BaseNestedTransactionFormset(BaseInlineFormSet):
def add_fields(self, form, index):
# allow the super class to create the fields as usual
super(BaseNestedTransactionFormset, self).add_fields(form, index)
try:
instance = self.get_queryset()[index]
pk_value = instance.pk
except IndexError:
instance=None
pk_value = hash(form.prefix)
transaction_data = None
if (self.data):
transaction_data = self.data;
# store the formset in the .nested property
form.nested = [
CategoryFormset(data=transaction_data,
instance = instance,
prefix = 'CAT_%s' % pk_value)]
def is_valid(self):
result = super(BaseNestedTransactionFormset, self).is_valid()
for form in self.forms:
if hasattr(form, 'nested'):
for n in form.nested:
# make sure each nested formset is valid as well
result = result and n.is_valid()
return result
def save_new(self, form, commit=True):
"""Saves and returns a new model instance for the given form."""
instance = super(BaseNestedTransactionFormset, self).save_new(form, commit=commit)
# update the form’s instance reference
form.instance = instance
# update the instance reference on nested forms
for nested in form.nested:
nested.instance = instance
# iterate over the cleaned_data of the nested formset and update the foreignkey reference
for cd in nested.cleaned_data:
cd[nested.fk.name] = instance
return instance
def save_all(self, commit=True):
"""Save all formsets and along with their nested formsets."""
# Save without committing (so self.saved_forms is populated)
# — We need self.saved_forms so we can go back and access
# the nested formsets
objects = self.save(commit=False)
# Save each instance if commit=True
if commit:
for o in objects:
o.save()
# save many to many fields if needed
if not commit:
self.save_m2m()
# save the nested formsets
for form in set(self.initial_forms + self.saved_forms):
# if self.should_delete(form): continue
for nested in form.nested:
nested.save(commit=commit)
ImportTransactionFormset = inlineformset_factory(Account, ImportData, exclude=('user_username',), formset=BaseNestedTransactionFormset, extra=0)
My template has a table that displays the import data formset... user selects the account and the table shows all the imported data from that account. For each of these row forms, there is a hidden row underneath... user clicks a button to show that hidden row. The hidden row displays the nested classification formset.
If include the user_username field in the template and allow for it to be part of the nested formset in the template, i can set is accordingly in the html form and the formsets save no problem.
However: I want to be able to exclude the user_username field from the template and have my view or some other method under the BaseNestedTransactionFormset class set the value of the user_username field to request.user value for whoever is logged in at that time.
I tried to override the clean method, but cleaned_data kicks back an error because the form doesnt validate; the field is required. I can't seem to figure out a good way to do this.
If this was a normal formset, not too hard to do. I would just set the field by modifying what comes back from POST. I have never worked with nested inline formsets, and the prefixes and indeces in the field names have got me. I've been at this for a couple of days and can't seem to be getting anywhere.
I am also contemplating just getting rid of that field from the classification model, since it is already tied to the ImportData model which is linked to the logged in user regardless. I'm just thinking i may run into this at some point again, so maybe good to solve.
Thanks in advance.

How to save choices value in django ?

I am donig with a poll system for my class. I use model-form and create-view to serve the poll form. I use choices in in the field but I just find out that create-view only save the last value of the checkboxes and I want to save all the selected choices as a list maybe. I've tried to change the form_valid() method, but I just find out that I need to iterate all the fields to check wheather there are multipule choices. It's not flexible. And I can't figure out other solutions...
How can I meet this requirement? I am truly a newbie..
Thanks in advance.
Thank the friend below for replying in such a short interval after I raised my question. Here is my code.
models.py
CHOICES = (('m','Math'),('f','French'),('s','Science'),('l','literature'))
class Poll(models.Model):
[...]
subject = models.CharField(max_length = 5,choices = CHOICES, blank=True)
[...]`
forms.py
class PollForm(forms.ModelForm):
model = Poll
fields = [..., 'subject', ...]
widgets = {'subject':forms.CheckboxSelectMultiple}
views.py
class PollView(CreateView):
form_class = PollForm
template_name = 'poll.html'
Students can choose subjects they want.
It seems like you need to convert your model. If you could provide a sample of the structure that you are using it would be helpful. Still lets try solving your query. First you need identify that choices is nothing more than a many to many field. Saving it in the db should be a bit easier that way. Lets try taking an example with choices for a user:
class Choices(models.Model):
description = models.CharField(max_length=100)
class UserProfile(models.Model):
user = models.ForeignKey(User, blank=True, unique=True, verbose_name='profile_user')
choices = models.ManyToManyField(Choices)
def __unicode__(self):
return self.name
Now if you want to make a default form you could simply do something like:
class ProfileForm(forms.ModelForm):
Meta:
model = UserProfile
Now comes your main view. This can be editted and rendered to whatever your use case demands it to be:
if request.method=='POST':
form = ProfileForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
#Implement this as a pre-save so that you can add additional value
profile.save()
else:
form = ProfileForm()
Hope this helps.

Two models in django

I am learning Django so I don´t know about this.
What is happening is that I have two tables.
Table BlogPost : Save all post.
Table Categoria : Save the ID of the category of register post.
My model.py
class BlogPost(models.Model):
title=models.CharField(max_length=150)
author = models.ForeignKey(User)
categorias_post = models.ManyToManyField(Categoria)
body = RichTextField(('Content of post'))
creada_en = models.DateTimeField(auto_now_add=True)
actualizada_al = models.DateTimeField(auto_now=True)
My forms.py
class FormularioPost(forms.ModelForm):
class Meta:
model = BlogPost
fields = ('title', 'author', 'categorias_post', 'body')
My views.py
def postregistrado(request):
if request.method == "POST":
form = FormularioPost(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save
messages.success(request, 'Su post ha sido registrado con éxito.')
else:
form = FormularioPost()
return render_to_response(
"postRegistrado.html",
locals(),
context_instance=RequestContext(request),
)
I want to insert in two different tables from the same views.py. Can anyone help me with that?
When you are using commit=False, you have to explicitly call save_m2m() to save the many to many fields.
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save() #Note that it is a function call.
post.save_m2m()
You can read more on this in the documentation here
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn’t possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you’ve manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
Another thing is, make sure you add the login_required decorator on this view, so that you don't run into weird issues when post.author = request.user evaluates to anonymous users

Django: saving dynamic forms with foreign key

I would like to seek assistance and guidance to my problem.
I have the following models:
class myinfo(models.Model):
name = models.CharField(max_length=30, null=True)
class mynumbers(models.Model):
fkey = models.ForeignKey("myinfo")
Job_Position = models.CharField(max_length=30, null=True)
The mynumbers model is dynamically generated via django-dynamic-formset.
My form
class info(ModelForm):
name= forms.CharField( max_length=20)
class Meta:
model = APPLICANT_DATA
fields = ('name',)
class numbers(ModelForm):
number = forms.CharField( max_length=20)
class Meta:
model = APPLICANT_DATA
fields = ('number',)
If you want to save your dynamic form fields you have to do this in views
for field in formset:
field.save()
My views:
def index(request):
aformset = formset_factory(numbers)
formset = aformset(request.POST)
form = info(request.POST)
if request.method == 'POST':
if form.is_valid():
if formset.is_valid():
for field in formset:
formset.save()
form.save()
But the problem starts when my dynamically generated field has a foreign key(mynumbers) which raises an error must be a myinfo instance. How would I save the 2 forms where mynumbers has a foriegn key to myinfo? Is there a better way to what I did? Thank you in advance,
This is where inlineformset_factory would be used. This allows you to have a parent model and a number of child models (related w/ parent via a foreignkey) and save them together. There are many arguments that can be passed to the inlineformset_factory in order to customize behavior (such as the minimum and maximum number of inline forms allowed, whether the user can delete inline forms, etc.) but the crux is shown below.
# views.py
from django.forms.models import inlineformset_factory
from my_app.forms import numbers as NumberForm
from my_app.forms import info as InfoForm
from my_app import models
myFormset = inlineformset_factory(models.myinfo,
models.mynumbers,
form=NumberForm
)
def index(request):
if request.POST:
form = InfoForm(request.POST)
if form.is_valid():
info = form.save(commit=False)
formset = myFormset(request.POST, instance=info)
if formset.is_valid():
info.save()
formset.save()
return HttpResponse('saved successfully')
else:
form = InfoForm()
formset = myFormset(instance=models.myinfo())
return render_to_response("recipes/submit.html", {
"form": form,
"formset":formset,
},
context_instance=RequestContext(request))
Please note: In your question, you typed for field in formset: formset.save(). A formset is a collection of forms, not a collection of fields.
Formsets can be tricky and require the template rendered correctly with additional template components that are not part of regular forms (such as the management_form variable that allows for Django to properly process what has been added/deleted/moved/changed). It's definitely worth doing some tutorials in order to get an idea of best practices so that you don't go down a troubleshooting rabbithole w/ your custom implementation.
I suggest this post from Charles Leifer as a good entry to get acquainted with the basics.

Categories