I have created a function that can save multiple goals per one user and display them in an html file. The issue is once I logout, I cannot log back in with the same user as I get the error User object has no attribute Goals, even though it is saved in the database. My question is what is causing this error, the references to goals in my view maybe, and what is a potential solution? Thank you!
models.py
class Goals(models.Model):
user = models.ForeignKey(User, null=True, default=None, on_delete=models.CASCADE)
goal = models.CharField(max_length=2000)
instrument = models.CharField(max_length=255, choices=instrument_list, blank=True)
goal_date = models.DateField(auto_now=False, auto_now_add=False)
def __str__(self):
return self.Goals
#receiver(post_save, sender=User)
def create_user_goals(sender, instance, created, **kwargs):
if created:
Goals.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_goals(sender, instance, **kwargs):
instance.Goals.save()
class GoalsForm(ModelForm):
class Meta:
model = Goals
exclude = ('user',)
views.py
def goal_creation(request):
form = GoalsForm()
cur_goals = Goals.objects.filter(user=request.user)
if request.method == 'POST':
form = GoalsForm(request.POST)
if form.is_valid():
goals = form.save(commit=False)
goals.user = request.user
goals.save()
cur_goals = Goals.objects.filter(user=request.user)
return redirect('/student/goal-progress')
else:
form = GoalsForm()
context = {'form' : form, 'goals': cur_goals}
return render(request, 'student/goal_creation.html', context)
You have two issues:
You can't access child instances using instance.Goals; you should use instance.goals_set.
You can't save a queryset. You should save Goals instances one by one, i.e.
for goal in instance.goals_set.all():
goal.save()
That being said, I recommend you to rename your Goals class to Goal as it will create confusion with Django's naming conventions. It also makes sense because each row represents a single goal.
Try adding related_name='goals' to user field definition of Goals class:
user = models.ForeignKey(User, related_name='goals', null=True, default=None, on_delete=models.CASCADE)
Then, you should be able to access this property on the user's object:
user_instance.goals.all().
Migration might be required.
Although this is not directly related to the issue, I think that it's better to name the model class in singular form "Goal", it will be consistent with other model's names (model represents one object=one row) and avoid ambiguity in automatic pluralization.
Related
I have a form that allows the user to pick several vans (many-to-many relationship). Each van has a boolean attribute named "available". I want to only show the vans whose "available" attribute is set to "True". How do I do this in the forms.py file?
I know that this could possibly be done in the template, but I did not want to create a new form-template with each individual field written out. I wanted to know if this functionality could be done in the forms.py file or in the class based view. I believe that doing it that way would be a bit cleaner. I've look into the validators but I don't think this is the way to go. Maybe I need to run a query set in the form file that checks the attribute before passing it to the form template?
views.py
def post(self, request):
"""Take in user data, clean it, and then post it to the database."""
form = self.form_class(request.POST) # pass in the user's data to that was submitted in form
if form.is_valid():
trip = form.save(commit=False) # create an object so we can clean the data before saving it
# now get the clean and normalize the data
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
trip_start = form.cleaned_data['trip_start']
trip_end = form.cleaned_data['trip_end']
van_used = form.cleaned_data['van_used']
trip.save()
forms.py
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
models.py
class trips(models.Model):
class Meta:
verbose_name_plural = "trips"
van_used = models.ManyToManyField(vans)
class vans(models.Model):
class Meta:
verbose_name_plural = "vans"
vanName = models.CharField(max_length=30, unique=True, blank=False)
available = models.BooleanField(default=True, blank=False)
# set up how the vans will be referenced in the admin page
def __str__(self):
return self.vanName
The final form that is rendered would only show the vans whose "available" attribute is set to True. Thanks in advance.
You have to override queryset for van_used field in form like this.
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['van_used'].queryset = vans.objects.filter(available=True)
I use a generic CreateView to let logged in users (Creator) add objects of the model Piece. Since creating a Piece is done by the Creator (logged in user) there is no need for the CreateView to either show or manipulate the 'creator' field. Hence I wish to not show it and set it to the logged in user. However, approaches such as overwriting form_valid or using get_form_kwargs seem not to get it done. Using the form_valid method gives a ValueError:
Cannot assign "<SimpleLazyObject: <User: patrick1>>": "Piece.creator" must be a "Creator" instance.
The solution seems to be just around the corner, I hope.
Tried but did not work:
form_valid method, form_valid method, get_form_kwargs method
My code:
models.py
class Piece(models.Model):
creator = models.ForeignKey('Creator', on_delete=models.SET_NULL, null=True)
title = models.CharField(max_length=200)
summary = models.TextField(max_length=1000, help_text='Enter a brief description of the Piece')
created = models.DateField(null=True, blank=True)
...
from django.contrib.auth.models import User
class Creator(models.Model):
...
user = models.OneToOneField(User, on_delete=models.CASCADE)
...
views.py
class PieceCreate(LoginRequiredMixin, CreateView):
model = Piece
fields = ['title', 'summary', 'created']
initial = {'created': datetime.date.today()}
def form_valid(self, form):
obj = form.save(commit=False)
obj.creator = self.request.user
return super(PieceCreate, self).form_valid(form)
success_url = reverse_lazy('pieces')
Any suggestions are highly appreciated!
obj.creator = Creator.objects.get(user=self.request.user)
or any other solution that will give you Creator instance for current user instead of User. Just as the error message says.
Cannot assign "User: patrick1": "Piece.creator" must be a "Creator" instance.
All pages in my Django website have a footer link "Feedback/Questions". If the new person comes to the site and clicks that link, they should be directed to a form with a pulldown to indicate if they have feedback versus a question and fields for their email address and their feedback or question. The page will have a simple header all non-authenticated users will see. On the other hand, if a site member signs in and is authenticated, they should see the same form but without the email field (since I already know their email address) and a different authenticated header containing the site's internal navbar, buttons, etc.
My initial thought was to create an abstract class FeedbackQuestion:
class FeedbackQuestion(models.Model):
submission_type = ... (type, i.e. feedback or question)
submission_text = ... (actual feedback or question)
...
class Meta:
abstract = True
Then I'd create two separate concrete child classes:
class AnonFeedbackQuestion(FeedbackQuestion):
email = models.EmailField(...)
class Meta:
db_table = anon_feedback_question
class AuthFeedbackQuestion(FeedbackQuestion):
user = models.ForeignKey(User, related_name="user")
class Meta:
db_table = auth_feedback_question
These two classes would have their own model forms:
class AnonFeedbackQuestionForm(ModelForm):
class Meta:
model = AnonFeedbackQuestion
fields = ['submission_type', 'submission_text', 'email']
class AuthFeedbackQuestionForm(ModelForm):
class Meta:
model = AuthFeedbackQuestion
fields = ['submission_type', 'submission_text']
The problem I forsee is that I will have to do the following in my view that displays the feedback form:
def get_feedback_questions(request, template):
if request.method == 'POST':
...
if request.user.is_authenticated():
form = AuthFeedbackQuestionForm(request.POST)
else:
form = AnonFeedbackQuestionForm(request.POST)
if form.is_valid():
(process form)
...
else:
if request.user.is_authenticated():
form = AuthFeedbackQuestionForm(request.POST)
else:
form = AnonFeedbackQuestionForm(request.POST)
...
context = {'form': form}
return render(request, template, context)
Having to repeat these if/then/else blocks to identify which form to use seems rather inelegant. Is there a better, cleaner "Django" way to do this?
Thanks!
I wouldn't subclass your models - if it's an anonymous question you could just include a user attribute as well as an email attribute on one model with blank=True and null=True:
class FeedbackQuestion(models.Model):
submission_type = ... (type, i.e. feedback or question)
submission_text = ... (actual feedback or question)
email = models.EmailField(..., blank=True, null=True)
user = models.ForeignKey(User, related_name="user", blank=True, null=True)
...
class Meta:
abstract = True
This way you can add either the email for an anonymous user's feedback/question or the user if they're authenticated.
Then I'd combine your forms into one including the email field, but remove the email field depending on if the user is authenticated (see this answer):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(UserForm, self).__init__(*args, **kwargs)
if self.user:
# For logged-in users, email field not necessary
self.fields.pop('email')
else:
# Otherwise, the field needs to be required
self.fields['email'].required = True
Then you just need to make sure you create the user appropriately as you clean the form's data (e.g., make sure the email address isn't already taken, etc.)
I am having some trouble in selecting data in Django.
models.py
class Location(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
my_location = models.CharField(max_length=120, choices=LOCATION_CHOICES)
update_date = models.DateField(auto_now=True, null=True)
date = models.DateField()
def __str__(self):
return self.my_location
class UserProfile(models.Model):
user = models.ForeignKey(User)
user_base = models.CharField(max_length=120, choices=LOCATION_CHOICES)
user_position = models.CharField(max_length=120)
user_phone = models.PositiveIntegerField()
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.user)
super(UserProfile, self).save(*args, **kwargs)
def __unicode__(self):
return self.user.username
views.py
def index(request):
locations = Location.objects.order_by('-update_date')
context = {'locations': locations}
return render(request, 'index.html', context)
I was able to show the email from User module but what I really want to show is the data from UserProfile.
Please, any advice.
Thank you.
Instead of using
user = models.ForeignKey(User)
use:
user = models.OneToOneField(User)
One-to-one relationships suit better your case. If you use them, your User model will automatically get a userprofile attribute that you can use like this:
>>> user = User.objects.get(...)
>>> user.userprofile.user_phone
12345
You can also consider writing a custom User model, so that you can get rid of UserProfile.
Bonus tip: PositiveIntegerField is not the right field for a phone number. Leading zeroes have a meaning. Also, PositiveIntegerField have a maximum value. Use CharField instead.
Use a OneToOneField
To make it more direct, I'd make the UserProfile have a OneToOneField relationship with User, instead of a ForeignKey. Because this will mean that a given user can only have one profile.
class Location(models.Model):
user = models.OneToOneField(User)
In which case you can access it easier with location.user.userprofile.your_field
Using a custom MyUser model
If you want to make this even more direct, you could make a custom MyUser model that will contain both the fields from User and UserProfile.
It would go roughly like this:
from django.contrib.auth.models import AbstractBaseUser
class MyUser(AbstractBaseUser):
# Adding your custom fields
user_base = models.CharField(max_length=120, choices=LOCATION_CHOICES)
user_position = models.CharField(max_length=120)
user_phone = models.CharField(max_length=120)
slug = models.SlugField()
class Location(models.Model)
user = OneToOneField(MyUser) # Using your custom MyUser model
This allows a more direct access, e.g. location.user.user_phone instead of location.user.userprofile.user_phone
I've only provided pseudocode, please refer to Django documentation
Using a ForeignKey means you may have multiple profiles
In the other case where a user may have multiple user profiles, you then have the burden on you to select which profile to use to pull the relevant data from, because then the relationship would be user.userprofile_set, a set that you will have to filter/index to choose from.
I saw another answer here and other places on the web that recommend using user.get_profile when extending the built-in django user. I didn't do that in the below example. The functionality seems to be working fine, but is there a downside for not using user.get_profile()?
model
class UserProfile(models.Model):
user = models.ForeignKey(User, primary_key=True)
quote = models.CharField('Favorite quote', max_length = 200, null=True, blank=True)
website = models.URLField('Personal website/blog', null=True, blank=True)
class UserProfileForm(ModelForm):
class Meta:
model = UserProfile
fields = ('quote', 'website')
view
#login_required
def user_profile(request):
user = User.objects.get(pk=request.user.id)
if request.method == 'POST':
upform = UserProfileForm(request.POST)
if upform.is_valid():
up = upform.save(commit=False)
up.user = request.user
up.save()
return HttpResponseRedirect('/accounts/profile')
else:
upform = UserProfileForm()
return render_to_response('reserve/templates/edit_profile.html', locals(), context_instance=RequestContext(request))
The code works as you've written it, but because you don't pass an instance to your model it's a bit unusual, so it might take another Django developer a bit longer to work out what's going on.
The view you link to instantiates the model form with an instance, so that the existing profile values are displayed in the form. In your case, you'll get empty fields.
upform = UserProfileForm(instance=user.get_profile())
Because you don't provide an instance, saving would try to create a new user_profile, which we wouldn't want. That won't happen in your case, because you've made user the primary key, but that's a little unusual as well.
The main advantage of writing user.get_profile() is that you don't need to know which model is used for the user profile. If you are happy to hardcode UserProfile model in your code, you could put instance=UserProfile.objects.get(user=user) instead.