Django: Extending User Model - Inline User fields in UserProfile - python

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'),
...

Related

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.

Django CBV Detailview

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

Multiple signup, registration forms using django-allauth

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

Django Inclusion Tag doesn't post to database

I'm trying to build a form to save Names and Email Adresses to my database. However, it doesn't save...
I've used an Inclusion Tag because I want to use the same form in different templates.
This is my models.py:
class Contact(models.Model):
FRAU = 'FR'
HERR= 'HR'
GENDER_CHOICES = (
(FRAU, 'Frau'),
(HERR, 'Herr'),
)
gender = models.CharField(max_length=2, choices=GENDER_CHOICES, default=FRAU)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=200)
email = models.EmailField()
def __unicode__(self):
return "%s %s" %(self.first_name, self.last_name)
This is my forms.py:
class FragenContactForm(ModelForm):
class Meta:
model = Contact
fields = ['gender', 'first_name', 'last_name', 'email']
This is my custom tags module:
from django import template
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from fragen.forms import FragenContactForm
register = template.Library()
#register.inclusion_tag('fragen/askforoffer.html', takes_context=True)
def askforoffer(context):
form = FragenContactForm(context['request'].POST or None)
if context['request'].method=='POST':
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('fragen/thanks.html'))
else:
messages.error(context['request'], "Error")
return {'form': FragenContactForm()}
After I fill in and submit the form, I see nothing in my database. Am I missing something?
Thanks!
I've used an Inclusion Tag because I want to use the same form in
different templates.
You can simply reuse the form - or as your form in this case is very simple, you can use the CreateView generic class based view and reduce your code even further.
Your view would contain just the following:
class OfferForm(CreateView):
template_name = 'fragen/askforoffer.html'
model = Contact
fields = ['gender', 'first_name', 'last_name', 'email']
success_url = 'fragen/thanks.html'
Django will automatically create the ModelForm, and handle the error redirection and saving of the fields for you.
In your fragen/askforoffer.html template, you need just this:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
Finally, in your urls.py:
url(r'^submit-offer/$', OfferForm.as_view(), name='offer-form')
To display the same form in multiple places, just map it to a URL:
url(r'^another-form/$', OfferForm.as_view(), name='another-form')
Finally, __unicode__ method should return a unicode object; so in your model:
def __unicode__(self):
return u"{} {}".format(self.first_name, self.last_name)
The way you are trying to do it will not work because the template tag code will be executed before the template is rendered; so by the time the user sees the form, your tag code is already finished. There is no way to "trigger" it again; which is why you need a traditional view method which will accept the data entered into the form.
Post is a method of server request which is handled by views.
Inclusion tag is rendered along with the page (that is during server response). Thus page context can not get request.POST - of cause, if you don't send POST deliberately as a context variable to the page (but it won't be request.POST - just some_variable). It looks a bit weird..
You have to handle form-processing in a view function.
from django.shortcuts import redirect, render
from fragen.forms import FragenContactForm
def askforoffer(request):
form = FragenContactForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('specify_thank_url_here')
return render(request, 'fragen/askforoffer.html',
{ 'form': form })
I've never seen any form processing in an inclusion tag and I doubt this will work. Above view-function may point you in the right direction.

django form exclude a user instance from a queryset

I have the following model:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name="user")
people_interested = models.ManyToManyField(User, related_name="interested")
Now I want a form where I want to offer users a form where they can choose people_interested, so I add the following forms.py
class ChooseForm(forms.Form):
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
and then in views:
form = ChooseForm(data = request.POST or None)
if request.POST and form.is_valid():
uprofile, created = UserProfile.objects.get_or_create(user=request.user)
uprofile.people_interested = form.cleaned_data['peers']
uprofile.save()
return HttpResponseRedirect("/")
else:
return render(request, "form_polls.html", {'form':form})
But the trouble with this is, the current user instance also gets displayed. So I tried the following in views.py:
form = ChooseForm(request.user.id, data = request.POST or None)
and then in forms.py
class ChooseForm(forms.Form):
def __init__(self, uid, *args, **kwargs):
super(ChooseForm, self).__init__(*args, **kwargs)
self.fields['peers'].queryset = User.objects.exclude(id=uid)
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
But the above is not a clean implementation, is there a better method of doing it??
What makes you say this is not a clean implementation? Overwriting queryset on __init__ is perfectly acceptable.
The only things I'd do to improve your code is using a post_save signal on User to create it's UserProfile, then just do user.get_profile() on your view. See this question
You could also use a ModelForm for UserProfile instead of a regular form, and limit the fields to people_interested.

Categories