Django: CreateView with additional field? - python

I am trying to program a Django CreateView (CBV), which takes instead of the user id the user email and determines (or creates) the user based on the email.
My model does not contain anything special:
class Project(models.Model):
name = models.CharField(_('Title'), max_length=100,)
user = models.ForeignKey(User, verbose_name=_('user'),)
...
My forms.py adds the additional email field to the form:
class ProjectCreateForm(forms.ModelForm):
email = forms.EmailField(required=True, )
class Meta:
model = Project
fields = ('name', ...,)
In my views.py, I am trying to determine if the user exists or should be created. In both cases, the user id should be saved as part of the Project instance.
class ProjectCreateDetails(CreateView):
form_class = ProjectCreateForm
template_name = '...'
success_url = reverse_lazy('login')
model = Project
def form_valid(self, form):
try:
user = User.objects.get(email=form.email)
except User.DoesNotExist:
user = User.objects.create_user(form.email, form.email, ''.join([random.choice(string.digits + string.letters) for i in range(0, 10)]))
user.save()
form.instance.user = user
return super(ProjectCreateDetails, self).form_valid(form)
However I am facing an error that the 'Solution' object has no attribute 'email'.
Do I need to switch to a FormView instead of a CreateView?

You get the error 'Solution' object has no attribute 'email' because form.email is invalid. Validated data is never available as attributes of a form or model form. When forms (including model forms) are valid, the successfully validated data is available in the form.cleaned_data dictionary.
Note that you don't need to call user.save(). The create_user call has already added the user to the database. You don't have to generate a random password either -- if password is None, then create_user will set an unusable password.
Finally, make sure that you do not include the user field in the ProjectCreateForm. You probably do not, but your code says fields = ('name', ...,) so I can't tell for sure.
Put it together and you get the following (untested) code:
def form_valid(self, form):
try:
user = User.objects.get(email=form.cleaned_data['email'])
except User.DoesNotExist:
user = User.objects.create_user(form.cleaned_data['email'], form.cleaned_data['email'])
form.instance.user = user
return super(ProjectCreateDetails, self).form_valid(form)

Related

Create a new model in Django linked tothe User table

I'm new to Django and I've correctly created my first web app where I can register and login as an user. I'm using the standard from django.contrib.auth.models import User and UserCreationFormto manage this thing.
Now, I would like to create a new table in the database to add new fields to the user. I'm already using the standard one such as email, first name, second name, email, username, etc but I would like to extend it by adding the possibility to store the latest time the email has been changed and other info. All those info are not added via the form but are computed by the backend (for instance, every time I want to edit my email on my profile, the relative field on my new table, linked to my profile, change value)
To do that I have added on my models.py file the current code
from django.db import models
from django.contrib.auth.models import User
class UserAddInformation(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
last_time_email_change = models.TimeField('Last email changed', auto_now_add=True, blank=True)
def __str__(self):
return self.user.username
And on my admin.py
from django.contrib import admin
from .models import UserAddInformation
admin.site.register(UserAddInformation)
The form to edit the email and the view can be found below
forms.py
class EditUserForm(UserChangeForm):
password = None
email = forms.EmailField(widget=forms.EmailInput(attrs={
'class': 'form-control'
}))
class Meta:
model = User
# select the fields that you want to display
fields = ('email',)
views.py
#authenticated_user
def account_user(request):
if request.method == "POST":
form = EditUserForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
user_info_obj = UserAddInformation.objects.create(user=request.user,
last_time_email_change=datetime.now())
user_info_obj.save()
messages.success(request, "Edit Succesfully")
else:
pass
else:
form = EditUserForm()
return render(request, 'authenticate/account.html', {
'form_edit': form,
})
The issue is that, once I'm going to update the email via the form, I got an error UNIQUE constraint failed: members_useraddinformation.user_id
Using ForeignKey make it works but it create a new row in the table, with the same when I just want to update the first one
The edit process for the email works tho
What am I doing wrong?
It turned out that auto_now_add=True inherit editable=False generating the error. So changing my models.py with
class UserAddInformation(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
last_time_email_change = models.TimeField('Last email changed')
def save(self, *args, **kwargs):
self.last_time_email_change = timezone.now()
return super(UserAddInformation, self).save(*args, **kwargs)
def __str__(self):
return self.user.username
and checking with
user_info_obj = UserAddInformation.objects.get_or_create(user=request.user)
user_info_obj[0].save()
inside def account_user(request): worked
I'm not sure it's the best solution for my issue tho

How do I add username of logged in user to model field in django

How to add username of currently logged in user to field in my model? For example, I need to store info about user like name, email and so on in model, other than default Django user model, but I still use default one to store credentials. I want to establish relationship between those, so I created username field in my model. How do I fill it with current user's username upon saving the corresponding form?
My model
class ApplicantProfile(models.Model):
name = models.CharField(max_length = 50)
dob = models.DateField()
email = models.EmailField()
description = models.TextField()
username = <something>
What do I change <something> with?
My form
class ApplicantProfileEdit(forms.ModelForm):
class Meta:
model = ApplicantProfile
fields = [
'name',
'dob',
'email',
'description',
]
My view
def ApplEditView(request):
form = ApplicantProfileEdit(request.POST or None)
if form.is_valid():
form.save()
form = ApplicantProfileEdit()
context = {
'form':form
}
return render(request, "applProfileEdit.html", context)
P.S. I tried to import models straight to my views.py, and assign request.user.username to username field of the model in my view, but it didn't work, just left that field empty. I had username as CharField when I tried this.
It is not a good idea to save the username itself, or at least not without a FOREIGN KEY constraint. If later a user changes their name, then the username now points to a non-existing user, if later another user for example changes their username to thatusername, then your ApplicantProfile will point to the wrong user.
Normally one uses a ForeignKey field [Django-doc], or in case each ApplicantProfile points to a different user, a OneToOneField [Django-doc]:
from django.conf import settings
from django.db import models
class ApplicantProfile(models.Model):
name = models.CharField(max_length = 50)
dob = models.DateField()
email = models.EmailField()
description = models.TextField()
# maybe a OneToOneField
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
In the view:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def appl_edit_view(request):
if request.method == 'POST':
form = ApplicantProfileEdit(request.POST)
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect('some-view-name')
else:
form = ApplicantProfileEdit()
context = {
'form':form
}
return render(request, 'applProfileEdit.html', context)
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

User Follower model on Django. Cannot use add() on a ManyToManyField which specifies an intermediary model. Use accounts.Contact's Manager instead

I am new to Django, Please forgive any silly mistakes in code or logic,
Intro: I am trying to create a user follower model in Django. Where users can follow and unfollow other users on the sites
Error: I have made the models for my follow/unfollow I have also made the views I am getting this error
AttributeError at /accounts/admin/follow/
Cannot use add() on a ManyToManyField which specifies an intermediary model. Use accounts.Contact's Manager instead.
The obj.followers.add(user) is highlighted in the traceback as the origin of the error
Below are my models.py
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
city = models.CharField(max_length=100)
country = models.CharField(max_length=100)
def get_absolute_url(self):
return reverse('accounts:profile', kwargs={'username': self.user.username})
class Contact(models.Model):
user_from = models.ForeignKey(User, related_name='suppporter')
user_to = models.ForeignKey(User, related_name='leader')
def __str__(self):
return '{} follows {}'.format(self.user_from, self.user_to)
User.add_to_class('following',
models.ManyToManyField('self', through=Contact, related_name='followers', symmetrical=False))
I think the models.py may be good. The fault I believe is in my views.
Below is my view.py
class FollowToggle(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
username = self.kwargs.get('username')
print(username + " This is the user who will be followed") # This prints correct
profile = get_object_or_404(Profile, user__username=username)
print(profile) # This prints correct
obj = get_object_or_404(User, username=username)
print(obj) # This prints correct
url_ = profile.get_absolute_url()
print(url_) # This prints correct
user = self.request.user
print(user) # This prints correct
if user.is_authenticated():
if user in obj.followers.all(): # I know this is the source of the error.
obj.followers.remove(user)
else:
obj.followers.add(user)
return url_
Below are the Urls.py just in case
url(r'^(?P<username>[-\w]+)/follow/$', views.FollowToggle.as_view(), name='follow'),
You cannot use add and remove method for manytomany relation defined through third model. From the docs:
Unlike normal many-to-many fields, you can’t use add(), create(), or set() to create relationships
Instead you should use Contact manager:
if user.is_authenticated():
if user in obj.followers.all(): # I know this is the source of the error.
Contact.objects.filter(user_to=obj, user_from=user).delete()
else:
Contact.objects.create(user_to=obj, user_from=user)
In Django 2.2 you can use add, remove and set methods (Docs)
You can also use add(), create(), or set() to create relationships, as long as your specify through_defaults for any required fields

Django NOT NULL constraint failed userprofile.user_id in case of uploading a file

I am trying to practice a simple project: A user registers (using Django registration-redux), uploads some file, and then s/he is provided with a list of her files, being downloadable. Here are my models.py, forms.py, and views respectively:
models.py
class UserProfile(models.Model):
user = models.ForeignKey(User, related_name='uploaded_by')
names = models.CharField(max_length=40)
lastname = models.CharField(max_length=50)
email = models.EmailField()
uploads = models.FileField(upload_to= 'blablabla')
def __str__(self):
return self.email
forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['names', 'uploads']
view.py
from .forms import UserProfileForm
from .models import UserProfile
#login_required()
def new(request):
form = UserProfileForm(request.POST or None, request.FILES or None)
if form.is_valid():
form.save()
context = {'title': 'welcome', 'form': form}
return render(requst, 'upform.html', context)
however when I login with a user and try to upload a file I get the error: IntegrityError at /new
NOT NULL constraint failed: _userprofile.user_id
After digging a lot I noticed someone suggested the reason of the error is because the user is not included anyhow in the process of posting the form, so I tried whatever came to my mind and the case in which I added the user field to the forms.py worked:
forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['names', 'uploads']
the problem however is that the form shown in the browser now includes a drop-down list containing all the registered users. I tried to associate the logged-in user with the form in the views by I kept seeing different errors.
My question is: How can I associate the uploads with the logged-in user in a transparent manner.
sorry if the question is too newbie-liked
Keep the user out of the form and add it on save:
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
I must say your model looks a bit odd; you have multiple profiles for each user, each with a single upload. Seems more likely you want a single profile, with a OneToOne relationship to User, than a separate Uploads model with a ForeignKey to UserProfile.
I added the user field to the forms.py worked:
This probably also opens up a security hole, because you could then set the user from outside of your application, overriding the logged-in user.
After digging a lot I noticed someone suggested the reason for the error is because the user is not included anyhow in the process of posting the form.
You figured that quite right. If the user is not a field, the form never knows how to fill the user field of UserProfile. Since you cannot save a UserProfile without a filled in user foreign key, you get the error.
There are various ways around this:
One way to solve the problem is to use save(commit=False) on the form, patch the user into the unsaved instance and manually saving the object to the database:
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
This slightly violates the encapsulation because you now handle the database save outside of the form.
You are also able to provide a initial "template" instance to the form:
form = UserProfileForm(
request.POST,
request.FILES,
instance=UserProfile(user=self.request.user)
)
You probably want to do so, anyway because the form also allows to edit an existing userprofile. Currently you are saving a new UserProfile each time and since user is not unique=True in your model class, you will get multiple profiles per user.
If you do not want this to be possible, check Daniel Roseman's answer, because then you probably want UserProfile.user to be a OneToOne field.
In this case, we can simplify to
profile, created = UserProfile.objects.get_or_create(user=request.user)
form = UserProfileForm(
request.POST,
request.FILES,
instance=profile
)
Note also that I removed the or None as it is not necessary. BaseForm (which ModelForm derives from) does this check for you (it actually does self.data = data or {}) which essentially undoes the prior none-ification)
You can do following the advice of Daniel.
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='uploaded_by')
names = models.CharField(max_length=40)
lastname = models.CharField(max_length=50)
email = models.EmailField()
class UserFiles(models.Model):
user = models.ForeignKey(UserProfile)
file = models.FileField(upload_to= 'blablabla')
Remember OneToOneField is similar a ForeignKey with the attribute unique=True.

Django register extended user

I'm trying to create and manage a custom user in django.
I saw there are two possibilities, and i've chosen to extend (not create a new auth).
Models
models.py
class Artists(models.Model):
user = models.OneToOneField(User)
artist_image = models.ImageField(null=True, blank=True, upload_to="/artist_image/")
def __str__(self):
return 'Profil de {0}'.format(self.username)
def get_absolute_url(self):
return reverse('artist-details', kwargs={'pk': self.pk})
As i read in doc, I just make a OneToOne field with the User class of django auth models, so I can access method and properties, such as username, email, on my own user (here Artists).
form.py
class CreateArtistForm(UserCreationForm):
class Meta:
model = User
fields = ('username', 'email')
def save(self, commit=True):
user = super(CreateArtistForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
Here I extend UserCreationForm to prepare a form a little different (I want to have email field on my register form).
But here is my question : I first tried with
class Meta:
model = Artists
fields = ('user.username', 'user.email')
But I the error fields unknown in model Artist.
So I tried just with username and email and same error.
So I changed the model = Artists to User, and it works fine.
But now how i register my Artist Object when the user is saved?
Do I have to make something like (in save()):
artist = Artists()
artist.user = user
artist.save()
Or override create_user()?
I'm quite lost here and i'm looking docs and questions not able to find something because most of example people define their own auth.
Thanks in advance
Besta
edit : i'm using django 1.8.2 and python 3.4

Categories