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.
Related
I'm creating a twitter-like app and I'm stuck on creating a UserProfileView which is supposed to display a certain User's profile, along with a list of posts made by that user below. Though I can't really figure out a way to create a proper view for that.
I'm trying to use class based views for that, the one I'll be inheriting from is probably DetailView (for profile model) and something inside of that which retrieves a queryset of posts made by that user:
My profile model looks like this:
class Profile(models.Model):
user = models.OneToOneField(
User, on_delete=models.CASCADE, primary_key=True)
display_name = models.CharField(max_length=32)
profile_picture = models.ImageField(
default='assets/default.jpg', upload_to='profile_pictures')
slug = models.SlugField(max_length=150, default=user)
def get_absolute_url(self):
return reverse("profile", kwargs={"pk": self.pk})
Post model:
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateField(auto_now_add=True)
content = models.TextField(max_length=280)
image = models.FileField(upload_to='post_images/', blank=True, null=True)
def __str__(self) -> str:
return f'Post by {self.author} on {self.date_posted} - {self.content[0:21]}'
def get_absolute_url(self):
return reverse("post-detail", kwargs={"pk": self.pk})
I've tried creating this method:
class UserProfileView(DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'users/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_posts'] = Post.objects.filter(author=Profile.user)
return context
But this one sadly doesn't work, raising an error of
"TypeError: Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardOneToOneDescriptor object at 0x000001A5ACE80250>."
'ForwardOneToOneDescriptor' object has no attribute 'id' is returned if I replace the filter argument with author=Profile.user.id
I'm not sure whether it's a problem with the way I filtered Posts, or how I used get_context_data.
The object is stored as self.object, so you can filter with:
class UserProfileView(DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'users/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_posts'] = Post.objects.filter(author_id=self.object.user_id)
return context
An alternative might be to use a ListView for the Posts instead, to make use of Django's pagination:
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
class UserProfileView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'users/profile.html'
paginate_by = 10
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.filter(author__profile__slug=self.kwargs['slug'])
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['profile'] = get_object_or_404(Profile, slug=self.kwargs['slug'])
return context
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.
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.
Hello Everybody excuse my english....
I am facing a problem with django.
I need to restrict object so only their owners can print it.
Model.py
class Post(models.Model):
title = models.CharField(max_length=50, blank=False)
prenom = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, null=False)
View.py
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
url.py
url(r'detail-post/(?P<pk>[-\d]+)$', views.detailpost.as_view(), name='detailpost'),
This works properly but the problem is that every users can access to the post of another user (http://localhost:8000/detail-post/1). So my question is how can i do some stuff befor rendering the page and see if the post belongs to the current user if yes we print it else we redirect the user to another page.
You can use the LoginRequiredMixin (new in Django 1.9) to make sure that only logged in users can access the view.
Then override the get_queryset method, and filter the queryset so that it only includes posts by the logged-in user.
from django.contrib.auth.mixins import LoginRequiredMixin
class DetailPost(LoginRequiredMixin, DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get_queryset(self):
queryset = super(DetailPost, self).get_queryset()
return queryset.filter(owner=self.request.user)
If the user views a post that does not belong to them, they will see a 404 page. If you must redirect the user instead of showing a 404, then you'll have to take a different approach.
Note that I have renamed your class DetailPost (CamelCase is recommended for classes in Django. You'll have to update your urls.py as well.
You can override get() or post() method in your view class.
from django.shortcuts import redirect
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get(self, request, *args, **kwargs):
self.post = Post.objects.get(pk=self.kwargs['pk'])
if self.post.user != request.user or not request.user.is_superuser:
return redirect('login')
else:
return super(detailpost, self).get(request, *args, **kwargs)
You should override 'get()' method in your 'detailpost' class, so that it would be something like below:
def get(self, request, *args, **kwargs):
queryset = self.model._default_manager.filter(user=request.user)
self.object = self.get_object(queryset)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
It seems like it is the only way to pass User from Request instance to filter queryset.
I did not find that DetailView uses self.request
The application I am working on needs a separate login for 2 different type of users. We need "clients" and "business" owners to be able to register.
For the "business" owner all that I need to do is set the boolean user.is_business to True
I have used ACCOUNT_SIGNUP_FORM_CLASS with a separate class that sets the boolean to true and that works like a charm.
But then the client login doesn't work anymore.
Is there a way to create a separate signup view for a different user?
I have tried the following
class BusinessUserRegistrationView(FormView):
form_class = BusinessSignupForm
template_name = 'allauth/account/signup.html'
view_name = 'organisersignup'
success_url = reverse_lazy(view_name)
organisersignup = BusinessUserRegistrationView.as_view()
And the form
class BusinessSignupForm(BaseSignupForm):
password1 = SetPasswordField(label=_("Password"))
password2 = PasswordField(label=_("Password (again)"))
confirmation_key = forms.CharField(max_length=40,
required=False,
widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(BusinessSignupForm, self).__init__(*args, **kwargs)
if not app_settings.SIGNUP_PASSWORD_VERIFICATION:
del self.fields["password2"]
def clean(self):
super(BusinessSignupForm, self).clean()
if app_settings.SIGNUP_PASSWORD_VERIFICATION \
and "password1" in self.cleaned_data \
and "password2" in self.cleaned_data:
if self.cleaned_data["password1"] \
!= self.cleaned_data["password2"]:
raise forms.ValidationError(_("You must type the same password"
" each time."))
return self.cleaned_data
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
user.is_business = True
adapter.save_user(request, user, self)
self.custom_signup(request, user)
setup_user_email(request, user, [])
return user
And in the urls.py
url(r'^organiser/$', 'authentication.views.organisersignup', name='organisersignup'),
The problem is that somehow, the boolean is_business is never set to True.
The from shows, I can save, but what is saved is never a business always a client. The BusinessSignupForm is a copy of the SignUpForm found in the allauth forms.
What am I doing wrong?
I'll answer the question as I found the solution to have multiple signup forms with allauth.
Form:
class BusinessSignupForm(SignupForm):
def save(self, request):
user = super(BusinessSignupForm, self).save(request)
user.is_organizer = True
user.save()
return user
View
class BusinessUserRegistrationView(SignupView):
template_name = 'allauth/account/signup-organizer.html'
form_class = BusinessSignupForm
redirect_field_name = 'next'
view_name = 'organisersignup'
success_url = None
def get_context_data(self, **kwargs):
ret = super(BusinessUserRegistrationView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
organisersignup = BusinessUserRegistrationView.as_view()
Template
<form id="signup_form" method="post" action="{% url 'organisersignup' %}">
{% csrf_token %}
{% bootstrap_form form %}
</form>
This can be reused over and over again to modify properties of the custom user model if you have one.
Currently running Django==1.8.10 and django-allauth==0.24.1
Instead of using user.is_business = True to differentiate types of users consider using a BusinessProfile class. You can have several profile types per user if necessary, for example a PartnerProfile, ClientProfile, SupplierProfile etc. Each profile type can have it's own signup, login, and profile pages.
Here are some alternative solutions:
Multiple user type sign up with django-allauth
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'),
...