Note: Django/Python beginner, hope this question is clear.
I'm creating a form where multiple instances of a model (Guest) can be edited at once in a single form, and be submitted at the same time.
This Guest model is linked to a parent model, Invite, meaning multiple Guests are attached to a single Invite.
I've managed to create a formset for each Guest and display it, but when I submit it, I get the following error.
NOT NULL constraint failed: app_guest.invite_id
I assume this is because it doesn't have an invite to save the data to, but I can't figure out where to put this invite_id it's asking for.
Here's my view so far:
def extra_view(request, code):
# Get the specific invite
invite = get_invite(code)
# Get guests attached to this invite
guests = invite.guest_set.all()
# Get the context from the request.
context = RequestContext(request)
# Store object of guests marked as attending
guests_attending = invite.guest_set.filter(attending=True, invite=invite)
form = ExtraForm(data=request.POST or None)
if form.is_valid():
# Save the new category to the database.
### FAILS HERE
form.save(commit=True)
# Go to Confirm page
HttpResponseRedirect('confirm')
else:
# The supplied form contained errors - just print them to the terminal for now
print form.errors
if guests_attending.count() > 1:
# If the request was not a POST, display the form to enter details.
guest_formset = modelformset_factory(Guest, form=ExtraForm, extra=0, max_num=guests_attending.count())
# Filter the formset so that only guests marked as attending are
filtered_guest_form = guest_formset(queryset=guests.filter(attending=True))
# Return the view
return render_to_response('weddingapp/extra.html', {'GuestForm': filtered_guest_form, 'invite': invite, 'guests_attending': guests_attending, 'form_errors': form.errors}, context)
else:
# Since there's no guests to create a form for, return Confirm view
return render(request, 'weddingapp/confirm.html', {
'invite': invite,
})
Here's my Guest model:
#python_2_unicode_compatible
class Guest(models.Model):
invite = models.ForeignKey(Invite, on_delete=models.CASCADE)
guest_name = models.CharField(max_length=200)
diet = models.CharField(max_length=250)
transport = models.NullBooleanField(default=False, null=True)
attending = models.NullBooleanField(null=True)
def __str__(self):
return self.guest_name
And here's the form:
class ExtraForm(forms.ModelForm):
diet = forms.CharField(max_length=128, help_text="Please enter your diet restrictions", required=False)
transport = forms.BooleanField(initial=False, help_text="Will you be needing transport?", required=False)
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Guest
fields = ('diet', 'transport')
Any help would really be appreciated. Even advice about a better way to structure all this would be helpful. Thanks.
Your form doesn't include the invite field, so you have to set it before saving to the database.
if form.is_valid():
# Save the new category to the database.
instance = form.save(commit=False)
instance.invite = invite
instance.save() # save to the db, now that we have set the invite
...
Related
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)
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.
i created a model
class ThisUser(models.Model):
created = models.DateTimeField(auto_now=True)
message = models.CharField(max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.user
I want to store message specifically for the user who is authenticated.
right now this will give me all user who is available in my user model.
Please help
user = models.OneToOneField(User, on_delete=models.CASCADE)
Instead of foriegn key use one to one relation
Well you are suppose to take care of that at view level and not model level,
for example this is how I create a new Album:
#login_required
def make_album(request):
if request.method == 'POST':
form = AlbumCreationForm(request.POST)
if form.is_valid():
new_album = core_models.Album(name=form.cleaned_data['name'], description=form.cleaned_data['description'], user=request.user)`
You can use request.user.id to get user's id for further use.
I am trying to make a notification app for my Django project.
I have this as one of my views:
class LikePostToggle(RedirectView):
def get_redirect_url(self,pk):
obj = get_object_or_404(UserPost,pk=pk)
user = self.request.user
if user.is_authenticated():
if user in obj.likes.all():
obj.likes.remove(user)
else:
obj.likes.add(user)
#Add notification to UserNotification model
#Auto fill all fields
return obj.get_absolute_url()
And this is my UserNotification model:
class UserNotification(models.Model):
user = models.ForeignKey(User,related_name='user',null=True)
post = models.ForeignKey('feed.UserPost',related_name='post-notification')
timestamp = models.DateTimeField(auto_now_add=True)
notify_type = models.CharField(max_length=6)
read = models.BooleanField(default=False)
def __str__(self):
return self.user
In the model I think I want the user field to be the user committing the action (Liking, commenting, etc.).
My question is how might I go above making it so that whenever someone likes a post or comments and therefore triggers the LikePostToggle view it also adds an instance to the UserNotification model so that the user can be notified?
You can create an instance with UserNotification() then call save(), or you can use the create shortcut.
In the view you have access to the post and the logged in user. The timestamp will be added automatically and read will default to False, so you just have to decide what to set notify_type to:
obj.likes.add(user)
#Add notification to UserNotification model
notification = UserNotification.objects.create(
user=self.request.user,
post=obj,
notify_type="???"
)
How can I use Django sessions to have a user be able to start a form on one page, and move to the next page and have them complete the form?
I have looked into pagination and wizard forms, but I don't get them at all.
When I have one page with a small portion of a model I'm using - in forms - and another page with the rest of the model - forms.py with the rest of the model info - I can use the first form perfectly.
But when I move to the next page, I get an error saying (1048, "Column 'user_id' cannot be null").
My best guess is to use Django sessions to fix this issue. I don't want the user to have to put in their username and password a second time to get this to work. Any ideas?
my models/forms.py:
class Contact(models.Model):
user = models.OneToOneField(User)
subject = models.CharField(max_length=100, blank=True)
sender = models.EmailField(max_length=100, blank=True)
message = models.CharField(max_length=100, blank=True)
def __str__(self):
return self.user.username
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = Contact
fields = ('username', 'password', 'email')
class ContactForm1(forms.Form):
class Meta:
model = Contact
fields = ('subject', 'sender')
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
class Meta:
model = Contact
fields = ('message',)
views:
def contact(request):
registered = False
if request.method =='POST':
user = UserForm(request.POST)
contact = ContactForm1(request.POST)
if user.is_valid() and contact.is_valid():
user = user.save()
user.set_password(user.password)
user.save()
contact = contact.save(commit=False)
contact.user = user
registered = True
return render(request, 'mysite/contact.html', {'user': user, 'contact': contact, 'registered': registered})
def contact_second(request):
if request.method =='POST':
contact = ContactForm2(request.POST)
if contact.is_valid():
contact = contact.save(commit=False)
contact.save()
return render(request, 'mysite/contact_two.html', {'contact': contact}
I think it's a good idea to use sessions to store the forms because you don't want on each page to store the user input into the database because what if s/he change mind in the 3rd page and s/he wants to discard the registration or whatever it is?
I think is better to store the forms in session until you get in the last page, you ensure that everything is valid and then save the data in the database.
So let's say that the bellow code is in one of the view that will serve the first form/page. You retrieve the data from the POST request and you check if the given data are valid using the form.is_valid(), you then store the form.cleaned_data (which contains the user's validated data) to a session variable.
form = CustomForm(request.POST)
...
if form.is_valid():
request.session['form_data_page_1'] = form.cleaned_data
Note here that you may need to add code to redirect the user to the next page if form.is_valid() is true, something like this:
if form.is_valid():
request.session['form_data_page_1'] = form.cleaned_data
return HttpResponseRedirect(reverse('url-name-of-second-page'))
Then in any other view let's say in the view that is going to serve the second page you can retreive the from data from the first page like this:
first_page_data = request.session['form_data_page_1']
And you can do whatever you want with them as if it was after you executed the form.is_valid() in the first view.