Django Form and some ForeingKey fields - python

Please tell me, when a model has a lot of related fields with other tables, how to make a normal form with filling such a model?
How do I create a form for Project?
class City(models.Model):
obl = models.CharField(max_length=255, choices=REGIONS, default="24", verbose_name="Регион")
name = models.CharField(max_length=128, verbose_name="Город")
population = models.IntegerField()
class Address(models.Model):
city = models.ForeignKey(City, on_delete=models.PROTECT, verbose_name="Город")
street = models.CharField(max_length=255, verbose_name="Улица")
numb = models.CharField(max_length=64, verbose_name="Номер дома")
class Project(models.Model):
manager = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="Сотрудник")
address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="Адрес")
vis = models.DateField(verbose_name="Подписан дата", blank=True)
accept = models.DateField(verbose_name="Принят дата", blank=True)
Maybe I need a step-by-step fill-in form

You can create and customize the admin form based on your needs. I.e. if you create an admin form for the Project and you would like to include the User form as an Inline form you can achieve that easily by inlines.
Please refer to the documentation regarding adding related objects inside forms.
https://docs.djangoproject.com/en/4.1/intro/tutorial07/

I would follow Jamal's answer if you only need the interface in the admin console.
If you want to create a custom interface in your own, I would do it like this (untested):
# forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['manager', 'vis', 'accept']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# When we're creating new instance
if not self.instance.pk:
self.address_form = AddressForm(
data=kwargs.get('data'),
files=kwargs.get('files'),
)
# When we're updating an existing instance
else:
self.address_form = AddressForm(
instance=self.instance.address,
data=kwargs.get('data'),
files=kwargs.get('files'),
)
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['street', 'numb']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# When we're creating new instance
if not self.instance.pk:
self.city_form = CityForm(
data=kwargs.get('data'),
files=kwargs.get('files'),
)
# When we're updating an existing instance
else:
self.city_form = CityForm(
instance=self.instance.city,
data=kwargs.get('data'),
files=kwargs.get('files'),
)
class CityForm(forms.ModelForm):
class Meta:
model = City
fields = '__all__'
# views.py
class CreateProject(CreateView):
model = Project
form_class = ProjectForm
template_name = 'project_form.html'
class UpdateProject(UpdateView):
model = Project
form_class = ProjectForm
template_name = 'project_form.html'
# project_form.html
<form method="post">
{% csrf_token %}
{# The project form #}
{{ form.as_p }}
<h2>Address</h2>
{# The address form in the project form. Instead of the address field. #}
{{ form.address_form.as_p }}
<h2>City</h2>
{# The city form in the address form. Instead of the city field. #}
{{ form.address_form.city_form.as_p }}
<input type="submit" value="Save" />
</form>
PS., if by any chance you're using django-crispy-forms package, make sure you don't render the form tags in the AddressForm and CityForm.
PSPS., if you come into a situation where two of the forms you're rendering on the same page have attributes with the same name, you will need to use prefix in your initializer of the forms to namespace these attributes. However, this is not the case in your question, so I didn't want to complicate.

Related

Django - how add User specific Items?

Good day Stackoverflow,
a user should be able to add multiple titles instead of always overwriting the one added title.
\\ views.py
def edit_profile(request):
try:
profile = request.user.userprofile
except UserProfile.DoesNotExist:
profile = UserProfile(user=request.user)
if request.method == 'POST':
form = UserProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
return redirect('/test')
else:
form = UserProfileForm(instance=profile)
return render(request, 'forms.html', {'form': form, 'profile': profile})
\\models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
title = models.CharField(max_length=1024)
def __str__(self):
return str(self.title)
\\forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('title',)
Then the user has a form on the website where he can add the specific title.
Until now, however, every time the user fills out the title form, the value in the database is overwritten.
As it should be:
When a new title is added in the form, it should simply be added to it.
At the end I should have the possibility, with a Foor loop in the HTML template, to display all the added titles of the respective user.
Do you know how to do this?
If you are using a relational database, this functionality isn't really supported for a single field. Though, if you really wanted to, you could use a JSON field to make this work.
However, it is probably a better idea to use a separate table for titles.
To do this, you need to create a new Title object like:
class Title(models.Model):
Then, create a many-to-one relationship using ForeignKey:
class Title(models.Model):
text = models.CharField(max_length=1024)
user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
The on_delete method is required. This particular one will delete all Titles associated with a UserProfile if a UserProfile is deleted.
Now, if you want to associate a title object with a UserProfile, you would do it like this:
profile = UserProfile(user=request.user)
title = Title.objects.create(text='My Very First Title', user_profile=profile)
For more info on many-to-one relationships in Django: https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_one/
You can create new model and assign new with the ForeignKey field.
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return ', '.join([title for title in self.titles.all()])
class UserTitle(models.Model):
title = models.CharField(max_length=1024)
userprofile = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='titles')
def __str__(self):
return self.title
views.py:
def edit_profile(request):
...
if request.method == 'POST':
...
if form.is_valid():
form.instance.userprofile = request.user.userprofile
form.save()
return redirect('/test')
...
admin.py:
from django.contrib import admin
from your_app.models import UserProfile
class UserProfileAdmin(admin.ModelAdmin):
list_display = ['id', 'user', '__str__']
admin.site.register(UserProfile, UserProfileAdmin)
settings.py:
INSTALLED_APPS = [
...
'your_app',
...
In template, to make for loop just use:
{% for title in user.userprofile.titles.all %}
{{ title }}
{% endfor %}
or if you need only User titles in single string:
{{ user.userprofile }}

Create an instance from TextInput in CreateView

I have a model.py file that has classes Author and Article. Article has a foreign key referencing Author. I have created a view,blogCreate, using a form ,ArticleForm, in my forms.py file. Since author in class Article is a foreign key, it means that author will be chosen from the Author queryset. This means that the select tag will automatically used by the form, instead I want to use the <input type="text" > tag so that I can create an instance of Author using the input and not select from the queryset.
forms.py
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'content', 'aurthor')
widgets = {
'title': forms.TextInput(attrs={
'class': 'title'}),
'content': forms.Textarea(attrs={
'class': 'text_input',
'name': 'article_content'}),
# Changed to TextInput so it can use <input type="text" >
'aurthor': forms.TextInput(attrs={
'class': 'text_input',
'name': 'aurthor_name'})
}
models.py
from django.db import models
from ckeditor.fields import RichTextField
class Aurthor(models.Model):
name = models.CharField("Author Name", max_length=100)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField("Title", max_length=100)
content = RichTextField(blank=True, null=True)
pub_date = models.DateTimeField("Publish Date", auto_now_add = True)
aurthor = models.ForeignKey(Aurthor, on_delete=models.CASCADE)
def __str__(self):
return self.title
views.py
from .models import Article, Aurthor
from django.views.generic import CreateView
from .forms import ArticleForm
class blogCreate(CreateView):
model = Article
form_class = ArticleForm
template_name = 'BlogHandler/blog.html'
blog.html
<form action="" method="post">
{% csrf_token %}
{{form.as_p}}
<button type="submit">Post</button>
</form>
I finally found a way to do exactly what I wanted, I don't if how efficient it is but it works. Let me know if there is a better way.
forms.py
from django import forms
from .models import Article, Author
class ArticleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
self.fields['author'] = forms.CharField(max_length=100, required=True)# author is required
class Meta:
model = Article
fields = ('title', 'content', )
In the question, I stated that I'd changed the widget for author to TextInput so that I could enter text not an instance of Author.This didn't work out the way I wanted, so instead I removed author from fields and made a custom field author that is not a field in my model. This way I still get the text input to create my Author instance.
models.py
class Article(models.Model):
title = models.CharField("Title", max_length=100, null=False)
content = RichTextField(blank=True, null=False)
pub_date = models.DateTimeField("Publish Date", auto_now_add = True)
author = models.ForeignKey(Author, on_delete=models.CASCADE, null=True)
I made author nullable in my models.py file but it is okay because I made the custom auhtor field in forms.py required so that all Articles made using the site have an Author. I was getting some error before this change
views.py
class articleCreate(CreateView):
model = Article
form_class = ArticleForm
def form_valid(self, form):
rt = super().form_valid(form)
article = form.save(commit=False)
author_name = self.request.POST['author'].title()
author, created = Author.objects.get_or_create(name=author_name)
article.author = author
article.save()
return rt
Here I first pause the save so that I can create an Author using the text input from the custom field author, which is simple text, if the Author instance already exists it gets else it creates it. Then I save and I'm done.

ModelMultipleChoiceField django and debug mode

i'm trying to implement a ModelMultipleChoiceField in my application, like that: Link
model.py
class Services(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=300)
class Professionals_Services(models.Model):
professional = models.ForeignKey(User, on_delete=models.CASCADE)
service = models.ForeignKey(Services, on_delete=models.CASCADE)
form.py
class ProfileServicesUpdateForm(forms.ModelForm):
service = forms.ModelMultipleChoiceField(required=False, queryset=Services.objects.all())
class Meta:
model = Professionals_Services
fields = ['service']
def clean(self):
# this condition only if the POST data is cleaned, right?
cleaned_data = super(ProfileServicesUpdateForm, self).clean()
print(cleaned_data.get('service'))
view.py
class EditProfileServicesView(CreateView):
model = Professionals_Services
form_class = ProfileServicesUpdateForm
context_object_name = 'services'
template_name = 'accounts/edit-profile.html'
#method_decorator(login_required(login_url=reverse_lazy('professionals:login')))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(self.request, *args, **kwargs)
def post(self, request, *args, **kwargs):
form = self.form_class(data=request.POST)
if form.is_valid():
services = form.save(commit=False)
services.save()
html
<select class="ui search fluid dropdown" multiple="" name="service" id="id_service">
{% for service in services_list %}
<option value="{{ service.id }}">{{ service.type }}</option>
{% endfor %}
</select>
For development i'm using Pycham Professionals(latest version) with docker, when i run the application and i try to make a POST the answer is:
Cannot assign "<QuerySet [<Services: Services object (2)>, <Services: Services object (5)>, <Services: Services object (6)>, <Services: Services object (7)>]>": "Professionals_Services.service" must be a "Services" instance.
But if i run the application in debug mode and with a breakpoints on the if form.is_valid():
the application works fine
That's because the validate is equal to Unknown not in debug
you know how to fix?
Your service is a ForeignKey:
service = models.ForeignKey(Services, on_delete=models.CASCADE)
A ForeignKey means that you select a single element, not multiple ones. You use a ManyToManyField [Django-doc] to select multiple elements:
class Professionals_Services(models.Model):
professional = models.ForeignKey(User, on_delete=models.CASCADE)
service = models.ManyToManyField(Service)
You should also not override the post method, and you can make use of the LoginRequiredMixin [Django-doc] to ensure that the user is logged in:
from django.contrib.auth.mixins import LoginRequiredMixin
class EditProfileServicesView(LoginRequiredMixin, CreateView):
login_url = reverse_lazy('professionals:login')
model = Professionals_Services
form_class = ProfileServicesUpdateForm
context_object_name = 'services'
template_name = 'accounts/edit-profile.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
In your Form you should also return the cleaned data:
class ProfileServicesUpdateForm(forms.ModelForm):
service = forms.ModelMultipleChoiceField(required=False, queryset=Services.objects.all())
class Meta:
model = Professionals_Services
fields = ['service']
def clean(self):
# this condition only if the POST data is cleaned, right?
cleaned_data = super(ProfileServicesUpdateForm, self).clean()
print(cleaned_data.get('service'))
return cleaned_data
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Models in Django are written in PerlCase, not snake_case,
so you might want to rename the model from Professionals_Services to ProfessionalService.
Note: normally a Django model is given a singular name, so Services instead of Service.

Form, Create and Update View for Many-To-Many Relationship

This is my first question here and I am writing because I'm going mad with this, even after reading documentation and a lot of answers here. Thank you very much and sorry for my bad english!
I have these models:
class Profile(models.Model):
name = models.CharField(max_length = 255, blank = False)
user = models.ForeignKey(User, blank = True, null = True)
class Category(models.Model):
name = models.CharField(max_length = 50, blank = False)
class ProfileCategory(models.Model):
profile = models.ForeignKey(Profile)
category = models.ForeignKey(Category)
class Meta:
unique_together = ('profile', 'category')
Is this model correct?
I suppose to have already the database with the categories saved. I need a page where the user can create a new profile and choose the categories from a list of checkbox. Should I use two form in the same page, one for the profile and one for choose the categories or a single form? I think i need a ModelMultipleChoiceField for the categories.
I also need a view that display the same form already filled with the profile and the categories where the user can change the profile name, and add or remove categories.
Tell me if you need more information and thank you very much.
ProfileCategory model is unnecessary here. Use ManyToMany field to achieve the same result:
class Category(models.Model):
name = models.CharField(max_length=50, blank=False)
class Profile(models.Model):
name = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, blank=True, null=True)
categories = models.ManyToManyField(Category, blank=True)
Now you can edit profile with a single form as any other model. The only thing you should to remember is to call save_m2m() in case if you override the form's save() method.
app/forms.py
from django import forms
from app.models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
widgets = {
'categories': forms.CheckboxSelectMultiple,
}
app/views.py
from django.views.generic.edit import CreateView
from app.forms import ProfileForm
from app.models import Profile
class ProfileCreate(CreateView):
form_class = ProfileForm
model = Profile
templates/app/profile_form.html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
app/urls.py
from app.views import ProfileCreate
urlpatterns = patterns('',
...
url(r'^profile/create/', ProfileCreate.as_view()),
)
To update profiles use UpdateView with the same ProfileForm class and template.
EDIT: If you need additional fields in the ProfileCategory model then you can set it as intermediary model with through argument of the ManyToManyField. To edit such models you have to use formsets. Read more about it here, here and here.

Django: Extending User Model - Inline User fields in UserProfile

Is there a way to display User fields under a form that adds/edits a UserProfile model? I am extending default Django User model like this:
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
about = models.TextField(blank=True)
I know that it is possible to make a:
class UserProfileInlineAdmin(admin.TabularInline):
and then inline this in User ModelAdmin but I want to achieve the opposite effect, something like inverse inlining, displaying the fields of the model pointed by the OneToOne Relationship (User) in the page of the model defining the relationship (UserProfile). I don't care if it would be in the admin or in a custom view/template. I just need to know how to achieve this.
I've been struggling with ModelForms and Formsets, I know the answer is somewhere there, but my little experience in Django doesn't allow me to come up with the solution yet. A little example would be really helpful!
This has been brought up before.
Here's a blog post with what I think is my favorite solution. The gist is to use two ModelForms, and render them into a single <form> tag in the template making use of the prefix kwarg:
http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/
Here's another method which I like a bit less, but is also valid. They use two separate <form>s on the page, with different actions and two submit buttons:
Proper way to handle multiple forms on one page in Django
This one talks more specifically about Users and UserProfiles:
How to create a UserProfile form in Django with first_name, last_name modifications?
Update
Here is what I ended up with
# models.py
class UserProfile(models.Model):
favorite_color = models.CharField(max_length=30)
user = models.OneToOneField(User)
# forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
# we fill the 'user' value in UserCreateView.form_valid
exclude = ('user',)
# views.py
from django.contrib.auth.forms import UserCreationForm
class UserCreateView(FormView):
# url to redirect to after successful form submission
success_url = reverse_lazy('user_list')
template_name = "userform.html"
def get_context_data(self, *args, **kwargs):
data = super(UserCreateView, self).get_context_data(*args, **kwargs)
data['userform'] = self.get_form(UserCreationForm, 'user')
data['userprofileform'] = self.get_form(UserProfileForm, 'userprofile')
return data
def post(self, request, *args, **kwargs):
forms = dict((
('userform', self.get_form(UserCreationForm, 'user')),
('userprofileform', self.get_form(UserProfileForm, 'userprofile')),
))
if all([f.is_valid() for f in forms.values()]):
return self.form_valid(forms)
else:
return self.form_invalid(forms)
def get_form(self, form_class, prefix):
return form_class(**self.get_form_kwargs(prefix))
def get_form_kwargs(self, prefix):
kwargs = super(UserCreateView, self).get_form_kwargs()
kwargs.update({'prefix': prefix})
return kwargs
def form_valid(self, forms):
user = forms['userform'].save()
userprofile = forms['userprofileform'].save(commit=False)
userprofile.user_id = user.id
userprofile.save()
return HttpResponseRedirect(self.get_success_url())
def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_context_data())
# userform.html
<form action="" method="POST" class="form">
{% csrf_token %}
{{ userform.as_p }}
{{ userprofileform.as_p }}
<button type="submit">Submit</button>
</form>
# urls.py
...
url(r'^create/$', UserCreateView.as_view(), name='user_create'),
...

Categories