Django models with OneToOne relationships? - django

Let's say I'm using the default auth.models.User plus my custom Profile and Address models which look like this:
class Profile(models.Model):
user = models.OneToOneField(User)
primary_phone = models.CharField(max_length=20)
address = models.ForeignKey("Address")
class Address(models.Model):
country = CountryField(default='CA')
province = CAProvinceField(default='BC')
city = models.CharField(max_length=80)
postal_code = models.CharField(max_length=6)
street1 = models.CharField(max_length=80)
street2 = models.CharField(max_length=80, blank=True, null=True)
street3 = models.CharField(max_length=80, blank=True, null=True)
Now I want to create a registration form. I could create a ModelForm based on User but that won't include fields for the Profile and Address (which are required). So what's the best way to go about building this form? Should I even use ModelForm at all?
Furthermore, how would I use the same form for editing the complex object? I could easily pass an instance of Profile back to it, which holds references to the necessary Address and Profile objects, but how do I get it to fill in the fields for me?

What about using 3 separate ModelForm. One for Address, one for User, and one for Profile but with :
class ProfileForm(ModelForm):
class Meta:
model = Profile
exclude = ('user', 'address',)
Then, process these 3 forms separately in your views. Specifically, for the ProfileForm use save with commit=False to update user and address field on the instance :
# ...
profile_form = ProfileForm(request.POST)
if profile_form.is_valid():
profile = profile_form.save(commit=False)
# `user` and `address` have been created previously
# by saving the other forms
profile.user = user
profile.address = address
Don't hesitate to use transactions here to be sure rows get inserted only when the 3 forms are valid.

You should look into the officially recommended way to extend the User model first, as seen in the docs, which I believe comes directly from the project manager's personal blog about the subject. (The actual blog article is rather old, now)
As for your actual issue with forms, have a look at the project manager's own reusable django-profiles app and see if perusing the code solves your issue. Specifically these functions and the views in which they are utilized.
Edited to Add:
I've looked into it a bit (as I needed to do so myself). It seems something like so would be sufficient:
# apps.profiles.models
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
...
birth_date = models.DateField(blank=True, null=True)
joined = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'user profile'
verbose_name_plural = 'user profiles'
db_table = 'user_profiles'
class Address(models.Model):
user = models.ForeignKey(UserProfile)
...
# apps.profiles.forms
from django import forms
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from django.contrib.auth.models import User
from apps.profiles.models import UserProfile, Address
class UserForm(ModelForm):
class Meta:
model = User
...
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
...
AddressFormSet = inlineformset_factory(UserProfile, Address)
I was using "..." to snip content in the code above. I have not yet tested this out but from looking through examples and the documentation on forms I believe this to be correct.
Note I put the FK from the Address model to the UserProfile and not the other way around, as in your question. I believe the inline formsets need this to work correctly.
Then of course in your views and templates you will end up treating UserForm, UserProfileForm, and AddressFormSet separately but they can all be inserted into the same form.

I think your are looking for inline formsets with model forms. This helps you to deal with multiple forms on one page and also takes care of foreign key relations.
Update:
Maybe this question helps you too: Django: multiple models in one template using forms

Related

create a user in Django 2.1 that is associated with an existing model

In my models.py file I have the following code ->
from django.db import models
class Blogger(models.Model):
username = models.CharField(max_length=20)
email = models.EmailField()
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
password = models.CharField(max_length=30, default='')
I want to associate the Blogger model with a User and create the User upon form submission. Here is the forms.py file ->
from django import forms
from blog.models import Blogger
class BloggerForm(models.ModelForm):
class Meta:
model = Blogger
fields = ['username', 'email', 'first_name', 'last_name', 'password']
And here is the views.py ->
class BlogView(FormView):
template_name = 'blogform.html'
form_class = BloggerForm
success_url = 'blog/'
How do I create a new user on the submission of this form ?
All fields in blogger already exists in User model, actually you don't need this Blogger model at all, just use the User model directly.
There's a couple ways you can do this but basically the general 2 answers are:
Toss/copy Django's user model and make your own (hard)
Extend the user model by making a new model, and relating it to the user model (easy)
I usually choose option #2 because then you don't have to reconfig the auth system. This is a good tutorial on how to do it: simpleisbetterthancomplex

Django Mezzanine - Simple custom Admin Form

I need to add job offers section to my company site (as a intro to django for me). The problem is that when i inherit my model from mezzanine's Page model it adds to admins create form all bunch of field which i dont need (like publish dates, draft field, comment field etc). I want to make create/edit job offers form as simple as possible.
I tried to inherit it from basic models.Model but it throws an error ...
Unknown column 'project_joboffer.id' in 'field list'"
I tried to customize Admin Form but im still getting error above.
models.py
class JobOffer(models.Model):
title = models.CharField(max_length=255, null=False, blank=False)
place = models.CharField(max_length=255, null=True, blank=True)
date = models.DateTimeField(auto_now_add=True)
content = models.TextField(blank=False,null=False)
published = models.BooleanField(default=True)
deleted = models.NullBooleanField()
forms.py
from django import forms
from ckeditor.widgets import CKEditorWidget
from models import JobOffer
class JobOfferForm(forms.ModelForm):
title = forms.CharField(max_length=255, required=True)
place = forms.CharField(max_length=255, required=False)
content = forms.CharField(required=True , widget=CKEditorWidget())
published = forms.BooleanField(initial=True)
deleted = forms.NullBooleanField()
# class Meta:
# model = JobOffer
admin.py
class JobOfferAdmin(admin.ModelAdmin):
form = JobOfferForm
admin.site.register(JobOffer, JobOfferAdmin)
OK, i fixed it. Migrations creating and deleting wasnt enough. I dont know why but this time i had to also delete entry in django_migrations table.

Extending User fields in UserCreationForm

I am trying to add some custom fields to a user, and extend the UserCreationForm so that I can add these fields when the user is created. I am following the docs but when I try to load the page to create a user I get an error: Unknown field(s) (username) specified for Customer.
The docs that I am following: Custom User and Auth Forms
models.py
class User(AbstractUser):
is_restaurant = models.BooleanField(default=False)
is_customer = models.BooleanField(default=False)
class Customer(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
address = models.CharField(max_length=200)
def __str__(self):
return self.user.get_full_name()
forms.py
class CustomerSignUpForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = Customer
fields = UserCreationForm.Meta.fields + ('address',)
I understand that username is not part of the Customer class, but the docs appear to be doing the same thing...
The doc says:
If your custom user model is a simple subclass of AbstractUser, then
you can extend these forms in this manner...
In other words this will work only in case you want to add to the form is_restaurant or is_customer fields:
class CustomerSignUpForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = User
fields = UserCreationForm.Meta.fields + ('is_restaurant',)
But in your case Customer is not subclass of AbstractUser, since this method is not working for you. As a workaround you can try to work with two separate forms in the same time as suggested in this answer.

dynamically add instances of a model in Django forms

I have the following model:
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class CalEvents(models.Model):
user = models.ForeignKey(User)
activity = models.CharField(max_length=256, unique=False, blank=True)
activity_type = models.CharField(max_length=30, unique=False, blank=True)
activity_code = models.CharField(max_length=30, unique=False, blank=True)
def __str__(self):
return "Activity, type and code for the calendar events of #{}".format(self.user.username)
What I would like to know is how can I dynamically add instances of that model with forms. I'm imagining something like having a form for the first instance (with fields "activity", "activity_type" and "activity_code") and then, if the user clicks a "plus sign" (or whatever), (s)he can add a second, third...Nth instance to the database.
You can achieve this functionality using Django's inline formsets.
https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#inline-formsets
It will allow you to declare a form which can be used dynamically to add multiple instances of a model.

Connect one model to another

I'm trying to make a simple shop for myself without popular modules. And stack on next things.
I have two models - articles (kind of "product" in here) and user with custom profile model. So what I need is when User goes to Article page, he can push the button ("Buy" maybe) and that article model connects to User. So he can see it on his profile page. Also, I need a check function in template, indicates that User bought Article or not (some kind "if-else").
I'm already hooked up my Article model to my User Profile model with ForeignKey, but right now I don't know where the next point to move. Can someone help?
My model userprofile:
import PIL
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
from django.db import models
from article.models import Article
class UserProfile(models.Model):
user = models.OneToOneField(User)
user_picture = models.ImageField(upload_to='users', blank=False, null=False, default='users/big-avatar.jpg')
user_balance = models.IntegerField(default=0)
user_articles = models.ForeignKey(Article, blank=True, null=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u) [0])
My forms.py for userprofile
from django import forms
from userprofile.models import User
from userprofile.models import UserProfile
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('email', 'first_name', 'last_name',)
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('user_picture', 'user_balance')
My view for userprofile
from django.shortcuts import render, render_to_response, redirect
from django.shortcuts import HttpResponseRedirect, Http404, HttpResponse
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from userprofile.forms import User
from userprofile.forms import UserForm
from userprofile.forms import UserProfileForm
def userprofile(request, username):
u = User.objects.get(username=username)
if request.POST:
user_form = UserForm(request.POST, instance=request.user)
user_profile = UserProfileForm(request.POST, request.FILES, instance=request.user.profile)
if user_form.is_valid() and user_profile.is_valid():
user_form.save()
user_profile.save()
else:
user_form = UserForm(instance=request.user,
initial={
'first_name': request.user.first_name,
'last_name': request.user.last_name,
'email': request.user.email,
})
user = request.user
profile = user.profile
user_profile = UserProfileForm(instance=profile)
return render_to_response('profile.html', {'user_form': user_form, 'user_profile': user_profile}, context_instance=RequestContext(request))
And my model article, that needs to be hooked up:
import PIL
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
from django.db import models
class Article(models.Model):
class Meta():
db_table = 'article'
article_title = models.CharField(max_length=200, blank=False, null=False)
article_anchor = models.CharField(max_length=200, blank=False, null=False)
article_image = models.ImageField(upload_to='items', blank=False, null=False)
article_users = models.IntegerField(default=0)
class Comments(models.Model):
class Meta():
db_table = 'comments'
comments_date = models.DateTimeField()
comments_text = models.TextField(verbose_name=u'')
comments_article = models.ForeignKey(Article)
comments_from = models.ForeignKey(User)
Just to clarify a few things:
I assume a user can purchase multiple articles
An article can belong to many users
If this is the case, then you have a many-to-many relationship between the user model and the article model. So what you can do is to modify your Article model:
class Article(models.Model):
class Meta():
db_table = 'article'
article_title = models.CharField(max_length=200, blank=False, null=False)
article_anchor = models.CharField(max_length=200, blank=False, null=False)
article_image = models.ImageField(upload_to='items', blank=False, null=False)
article_users = models.ManyToManyField(User) # <- use ManyToManyField instead of IntegerField
Another approach is to create a OrderHistory model to store who (User) purchased what(Article):
class OrderHistory(models.Model):
user = models.ForeignKey(User)
article = models.ForeignKey(Article)
purchase_date = models.DateTimeField(auto_now_add=True)
Let's assume that you used the first approach. Modifying models is not enough. There are a few things you need to add to your site:
A webpage for displaying a list of Articles for users to purchase
So you need a template file that shows a list of avaiable articles
you need a view function to render this page
this page will contain a list of articles and a way for users to select which article they want to buy via checkbox (or many buy buttons beside each article, your choice). So bascially your template will have a element that contains a list of articles and a 'buy' button to POST this data back to server
When a user clicks on the 'Buy' button, the data is submitted to a url that you need to define in the urls.py
add a new url in your urls.py and hook it to a view function
the view function will use request.user to identify which user it is and use the data in request.POST to figure out the article ids that's being purchased.
then you need to find the article from the database using
article = Article.objects.filter(pk=the_id_you_received_from_POST)
article.article_users.add(request.user)
article.save()
return a success message
Read this link before you start:
https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_many/
EDIT:
As Daniel pointed out, remove the following line from UserProfile
user_articles = models.ForeignKey(Article, blank=True, null=True)
You have to think about if the relationship between a user and an article is one-to-many or many-to-many. models.ForeignKey means one user can buy many articles, but once an article has been purchased, it can only belong to one user.(which is probably not what you want)
To pass data from a webpage to your view function, there are two ways:
Through GET request: parameters are appended to the end of the url, here is a good example of how it is done in Django: Capturing url parameters in request.GET
Through POST request: usually, you would have a form on the page and a submit button to submit the data to a predefined URL:
<form action = "url_for_handling_POST_request" method = "post">
Please follow Django's tutorial:
- https://docs.djangoproject.com/en/1.8/intro/tutorial04/
- https://docs.djangoproject.com/en/1.8/topics/forms/#processing-the-data-from-a-form
In your case, you should use POST request. so read the documentation above. It contains an example that matches to what you need.
Note: don't forget to insert the CSRF token inside your form or Django will complain:
<form ...>
{% csrf_token %}
</form>