I have model from which I created a ModelForm:
models.py:
class City(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return f'{self.name}'
class Profile(models.Profile):
name = models.CharField(max_length=50)
user = models.OneToOneField(User, on_delete=models.CASCADE, unique=False)
location = models.ForeignKey('City', on_delete=models.SET_NULL, blank=True, null=True)
forms.py
from django import forms
from .models import Profile, City
class LocationField(forms.CharField):
def clean(self, value):
try:
city = City.objects.get(name=value)
except ObjectDoesNotExist:
city = City.objects.create(name=value)
return city
class ProfileForm(forms.ModelForm):
location = LocationField()
class Meta:
model = Profile
exclude = ['user']
views.py
def profile_update_view(request):
template_name = 'profiles/update.html'
user = request.user
profile = Profile.objects.get(user__id=user.id)
if request.method == 'GET':
form = ProfileForm(instance=profile)
else:
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
obj = form.save(commit=False)
obj.user = user
obj.save()
return redirect('profile_view')
context = {'form': form}
return render(request, template_name, context=context)
When I'm saving form, I'm satisfied how it's working, but when I load form again to update in, it fills LocationField() as an City pk integer, but I want it to load name instead. Is there a way to do this?
I've added in views.py:
if request.method == 'GET':
initial = {}
if profile.location:
initial = {'location': profile.location.name}
form = ProfileForm(instance=profile, initial=initial)
now it's working. But it's some workaround. I've thought there is some parameter maybe
Related
i have a an order model which is in m2m relationship with a product model, when i create an order, and after checking my DB, i can see the order saved but not in the through model
models.py
from inventory.models import Product
from user.models import User
class Order(models.Model):
product = models.ManyToManyField(Product, through='OrderItems' )
vendeur = models.ForeignKey(User, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
class Customer(models.Model):
full_name = models.CharField(max_length=60, verbose_name='full name')
address = models.CharField(max_length=255)
phone = models.CharField(max_length=20)
city = models.CharField(max_length=30)
class OrderItems(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
order = models.ForeignKey(Order,on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True)
views.py
#login_required
def add_order(request):
if request.method == 'POST':
form = NewOrderForm(request.POST)
if form.is_valid():
order = form.save(commit=False)
order.vendeur = request.user
order.save()
return redirect('dashboard-index', )
else :
form = NewOrderForm()
return render(request, 'dashboard/add_order.html', {'form': form})
forms.py
class NewOrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ('product','quantity')
if you use save(commit=False), Calling save_m2m() is required.
because your form has a m2m field
refer to documentions
#login_required
def add_order(request):
if request.method == 'POST':
form = NewOrderForm(request.POST)
if form.is_valid():
order = form.save(commit=False)
order.vendeur = request.user
order.save()
form.save_m2m()
return redirect('dashboard-index', )
else :
form = NewOrderForm()
return render(request, 'dashboard/add_order.html', {'form': form})
I have models like this:
class Projects(models.Model):
project = models.CharField(max_length=150, blank=True, null=True)
def __str__(self):
return self.project
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
project = models.ForeignKey(Projects, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.user.username
I also made a form in which I want to change current Project assigned Profile:
class ProjectForm(ModelForm):
class Meta:
model = Profile
fields = [
'project'
]
My view looks like this:
def change_project(request, user):
user = User.objects.filter(username=user)[:1].get()
profile = Profile.objects.get(user=user)
form = ProjectForm(request.POST, instance=profile)
if request.method == 'POST':
if form.is_valid():
form.save()
context = {'form': form}
return render(request, 'datafiller/change_project.html', context)
I can change the project using this form, but every time I want to do it again the form looks like this
How can I show in the form the current project instead of "------"?
You should not ground the ProjectForm in case you render the form with a GET requuest:
from django.shortcuts import get_object_or_404
def change_project(request, user):
profile = get_object_or_404(Profile, user__username=user)
if request.method == 'POST':
form = ProjectForm(request.POST, instance=profile)
if form.is_valid():
form.save()
else:
form = ProjectForm(instance=profile)
return render(request, 'datafiller/change_project.html', {'form': form})
I am new in Django, would you please help me, I have two models, by name of Continent and Country, in the Country form I want to only display the Continents in the dropdown list which their status is true?
models
from django.db import models
from smart_selects.db_fields import GroupedForeignKey, ChainedForeignKey
class Continent(models.Model):
name = models.CharField(max_length=255)
status=models.BooleanField(default=True)
def __str__(self):
return self.name
class Country(models.Model):
continent = models.ForeignKey(Continent, null=True, on_delete=models.SET_NULL)
status=models.BooleanField(default=True)
name = models.CharField(max_length=255)
def __str__(self):
return self.name
forms
class FormContinent(ModelForm):
class Meta:
model = Continent
fields = '__all__'
class FormCountry(ModelForm):
class Meta:
model = Country
fields = '__all__'
views
def continent(request):
form = FormContinent()
if request.method == 'POST':
form = FormContinent(request.POST)
form.is_valid()
form.save()
return redirect('/continent')
else:
context = {'form': form}
return render(request, 'continent.html', context)
def country(request):
form = FormCountry()
if request.method == 'POST':
form = FormCountry(request.POST)
form.is_valid()
form.save()
return redirect('/country')
else:
context = {'form': form}
return render(request, 'country.html', context)
You can make use of the limit_choices_to=… parameter [Django-doc]:
class Country(models.Model):
continent = models.ForeignKey(
Continent,
# ↓ limit choices to Continents with status=True
limit_choices_to={'status': True},
null=True,
on_delete=models.SET_NULL
)
# …
I am using the same form for profile_edit and create_profile functionality. It is updating the multi-choice values in the profile_edit page but does not create in create_profile.
Below is the form code in forms.py
class ProfileForm(ModelForm):
full_name = forms.CharField(required=True)
current_position = forms.CharField(required=True)
about_me = forms.Textarea(attrs={'required':True})
topic_name = forms.ModelMultipleChoiceField(Topic.objects.all())
class Meta:
model = Profile
fields =(
"full_name",
"current_position",
"about_me",
"topic_name",
)
Below is the views.py for profile creation
def create_profile(request, user_id):
if request.method == "POST":
form = ProfileForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
user = get_object_or_404(User, id=user_id)
form.user = user
print(form.topic_name.all()) # Prints empty queryset
form.save()
return redirect("profile_view", user_id=user_id)
else:
context = {"form": form}
return render(request, "profile/create_profile.html", context)
else:
form = ProfileForm()
context = {
"form": form
}
return render(request, "profile/create_profile.html", context)
Below is Model.py
class Topic(models.Model):
topic = models.CharField(max_length=12)
def __str__(self):
return self.topic
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True,)
full_name = models.CharField(max_length=60, null=True)
current_position = models.CharField(max_length=64, null=True)
about_me = models.TextField(max_length=255, null=True)
topic_name = models.ManyToManyField(Topic)
def __str__(self):
return self.full_name
Both create_profile and edit_profile templates are exactly the same.
It saves everything except Multichoice field.
When you do save(commit=False),
you need to use mymodelform.save_m2m() below save(commit=True) on your ModelForm,
because many to many relationships cannot be saved without an ID.
see this docs
so in your views.py
if form.is_valid():
profile = form.save(commit=False)
user = get_object_or_404(User, id=user_id)
profile.user = user
profile.save()
form.save_m2m()
return redirect("profile_view", user_id=user_id)
In my Django project i create an app to have additional information about registered users. So my model looks like this:
class UserProfile(models.Model):
class Meta:
verbose_name_plural = u'User Profile'
user = models.OneToOneField(User)
birthday = models.DateField(blank=True, null=True)
avatar = models.ImageField(upload_to='media/profile/avatar', blank=True, null=True)
name = models.CharField(blank=True, null=True, max_length=20)
surname = models.CharField(blank=True, null=True, max_length=50)
phone = models.CharField(blank=True, null=True, max_length=12)
def __unicode__(self):
return '%s' % self.user
In user profile i create modelform where user can fill or edit the fields from UserProfile model:
class ExtraProfileDataForm(ModelForm):
name = forms.CharField(label=(u'Enter your name'))
surname = forms.CharField(label=(u'Enter your surname'))
phone = forms.CharField(label=(u'Enter your phone'))
birthday = forms.DateField(label=(u'Enter birthday'))
avatar = forms.ImageField(label=(u'Enter avatar'))
class Meta:
model = UserProfile
fields = ('name', 'surname', 'phone', 'birthday', 'avatar')
def __init__(self, *args, **kwargs):
super(ExtraProfileDataForm, self).__init__(*args, **kwargs)
for key in self.fields:
self.fields[key].required = False
This is the view of the model form:
#login_required
def UserFullDataForm(request):
if request.method == 'POST':
form = ExtraProfileDataForm(request.POST)
if form.is_valid():
profile_user = request.user
user_profile = UserProfile(user=profile_user)
user_profile.name = form.cleaned_data['name']
user_profile.surname = form.cleaned_data['surname']
user_profile.phone = form.cleaned_data['phone']
user_profile.birthday = form.cleaned_data['birthday']
user_profile.avatar = form.cleaned_data['avatar']
user_profile.save()
return HttpResponseRedirect('/profile/')
else:
return render(request, 'profiles/extra_profile.html', {'form':form})
else:
form = ExtraProfileDataForm()
context = {'form':form}
return render (request, 'profiles/extra_profile.html', context)
But i want to load on ExtraProfileDataForm initial data from model UserProfile if the fields not empty. I searched how to do that on Django documentation website, but nothing found. Can somebody help me to understand how to do it? Thanks a lot.
You use the instance parameter.
Note that you are doing much more work than necessary here; most of your view can be cut.
#login_required
def UserFullDataForm(request):
try:
profile = request.user.userprofile
except UserProfile.DoesNotExist:
profile = UserProfile(user=request.user)
if request.method == 'POST':
form = ExtraProfileDataForm(request.POST, instance=profile)
if form.is_valid():
form.save()
return HttpResponseRedirect('/profile/')
else:
form = ExtraProfileDataForm(instance=profile)
return render(request, 'profiles/extra_profile.html', {'form':form})
Similarly, in your form, you don't need the overridden __init__ method because you're manually specifying all the fields anyway; you can add required=False on each one there. However, you could make this even shorter by adding the labels in the model definition; then your entire modelform could just be:
class ExtraProfileDataForm(ModelForm):
class Meta:
model = UserProfile
fields = ('name', 'surname', 'phone', 'birthday', 'avatar')
One final note: you're consistently using three-space indentation, which is a bit, well, odd. Most Python programmers prefer two or four.