Django: Unable to access attribute of foreign key - django

I have a Comment model with User as a foreign key, and simply want to render the username of the comment. The comment shows up on the page but the username doesn't, I've tried comment.user and comment.user.username and neither works and I'm not sure why.. Sorry if this is a really basic question.
class Comment(models.Model):
comment = models.TextField(default="")
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE, default=1)
def comment(request, listing_id):
listin = Listing.objects.get(id=listing_id)
if request.method == "POST":
comment = CommentForm(request.POST)
if comment.is_valid():
comment = comment.save(commit=False)
comment.user = request.user
comment.listing = listin
comment.save()
return redirect("listing", listing_id=listing_id)
HTML code:
{% for comment in comments %}
<p>{{ comment.user.username }}: {{ comment.comment }}</p>
{% endfor %}
Edit:
I have sinced removed the blank=True and null=True arguments for user in the Comment model so it looks like this, migrated the changes and added a few comments, the username still doesn't render even though the username shows up as a field value in the admin view for the Comment object.
class Comment(models.Model):
comment = models.TextField(default="")
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
listing = models.ForeignKey(Listing, on_delete=models.CASCADE, default=1)
My User class simply inherits from the AbstractUser model
class User(AbstractUser):
watchlist = models.ManyToManyField(Listing)
def __str__(self):
return self.username

Assuming that User is the admin user, if you are not already logged as admin then the comment will be saved without a user as request.user is None.
You can force the user to be logged in by adding the login_required decorator before the view function
from django.contrib.auth.decorators import login_required
...
#login_required
def view(request)
...
This way you can only comment if you are already logged in and if not, then you will be redirected to the login page and once you are logged in the comment will be saved.

I see there you have no user data, you have to do this to prevent user comments from being available
from django.contrib.messages import error
from django.urls import reverse
from django.contrib.auth.decorators import login_required
#login_required
def comment(request):
if not request.user.is_authenticated:
error("....")
return redirect(reverse("foo"))
...

Related

How to store user auto in database?

I created a form for adding products to an e-Commerce site. The form isn't working perfectly.
First issue: I want to store the user automatically by submitting the form. I actually want to store Who did add the product individually.
Second Issues: The image field is not working, the image is not stored in the database.
How can I fix these issues? help me
forms.py:
from django import forms
from .models import Products
from django.forms import ModelForm
class add_product_info(forms.ModelForm):
class Meta:
model = Products
fields = ('product_title','product_image')
model.py:
class Products(models.Model):
user = models.ForeignKey(User, related_name="merchandise_product_related_name", on_delete=models.CASCADE, blank=True, null=True)
product_title = models.CharField(blank=True, null=True, max_length = 250)
product_image = models.ImageField(blank=True, null=True, upload_to = "1_products_img")
views.py:
def add_product(request):
if request.method == "POST":
form = add_product_info(request.POST)
if form.is_valid():
form.save()
messages.success(request,"Successfully product added.")
return redirect("add_product")
form = add_product_info
context = {
"form":form
}
return render(request, "add_product.html", context)
templates:
<form action="" method="POST" class="needs-validation" style="font-size: 13px;" novalidate="" autocomplete="off" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<div class="d-flex align-items-center">
<button type="submit" class="btn btn-outline-dark ms-auto" style="font-size:13px;">Add</button>
</div>
</form>
You need to set the .user of the .instance wrapped in the form to the logged in user (request.user). Furthermore you need to pass both request.POST and request.FILES to the form to handle files.
from django.contrib.auth.decorators import login_required
#login_required
def add_product(request):
if request.method == 'POST':
form = add_product_info(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
messages.success(request, 'Successfully product added.')
return redirect('add_product')
else:
form = add_product_info()
context = {
'form': form
}
return render(request, 'add_product.html', context)
I would also advise not to use null=True nor blank=True, unless a field is really optional. Likely the product_title should not be optional, nor should the user be, since you use CASCADE in case the user is removed.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
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: Forms in Django are written in PascalCase, not snake_case,
so you might want to rename the model from add_product_info to ProductInfoForm.
Note: normally a Django model is given a singular name, so Product instead of Products.
why are you using the ForeignKey with your user. the first issue i notice is with the class Meta. Pass this as a list not tuple.
class add_product_info(forms.ModelForm):
class Meta:
model = Products
fields = [
'product_title',
'product_image',
]
then try this as well.
class Products(models.Model):
user = models.OneToOneField(User, related_name="merchandise_product_related_name", on_delete=models.CASCADE, blank=True, null=True)

How do I include my def clean_slug function into my views or template so that it will work and show "title alr exist"

Hi I have written a def clean(self) function in forms to make sure that if there was previously already a post with the same title, they will return a message saying that the title already exists and hence the form cannot be submitted.
Problem now:
When I enter a title that already exists and I try to create the post, all data previously input will be removed and I will be redirected to a fresh form. No errors were raised. What I want is for the error to be raised and shown to the user when I try to click on the create button so all data remains there and the user knows and can change the title before attempting the create the blog post again.
return cleaned_data in forms.py is not defined too...giving a nameerror
Guideline:
Note! There is NO slug field in my form. The slug is only for the url for each individual blogpost. But basically the slug consists of the title. Eg if my username is hello and my chief_title is bye, my slug will be hello-bye. Both the slug and the title has to be unique as you can see in the model, but there is no slug in the form.
models.py
class BlogPost(models.Model):
chief_title = models.CharField(max_length=50, null=False, blank=False, unique=True)
brief_description = models.TextField(max_length=300, null=False, blank=False)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(blank=True, unique=True)
views.py
def create_blog_view(request):
context = {}
user = request.user
if not user.is_authenticated:
return redirect('must_authenticate')
if request.method == 'POST':
form = CreateBlogPostForm(request.POST or None, request.FILES or None)
if form.is_valid():
obj= form.save(commit = False)
author = Account.objects.filter(email=user.email).first()
obj.author = author
obj.save()
obj.members.add(request.user)
context['success_message'] = "Updated"
return redirect('HomeFeed:main')
else:
form = CreateBlogPostForm()
context['form'] = form
return render(request, "HomeFeed/create_blog.html", {})
forms.py
class CreateBlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['chief_title']
def clean(self):
chief_title = self.cleaned_data['chief_title']
qs = BlogPost.objects.filter(chief_title=chief_title)
if qs.exists():
raise forms.ValidationError('Post already exists')
return cleaned_data
html
<form class="create-form" method="post" enctype="multipart/form-data">{% csrf_token %}
{% if form.non_field_errors %}
{{form.non_field_errors}}
{% endif %}
<!-- chief_title -->
<div class="form-group">
<label for="id_title">Chief Title!</label>
<input class="form-control" type="text" name="chief_title" id="id_title" placeholder="Title" required autofocus>
</div> {{form.chief_title.errors}}
<button class="submit-button btn btn-lg btn-primary btn-block" type="submit">CREATE</button>
</form>
Well you can create a custom validator to check if the slug exists:
from django.core.exceptions import ValidationError
def validate_slug_exists(value):
blog_post = BlogPost.objects.filter(slug=value)
if blog_post.exists():
raise ValidationError('The post with a given title already exists')
Then in your form add this validator to the fields validators list:
class CreateBlogPostForm(forms.ModelForm):
chief_title = forms.CharField(validators = [validate_slug_exists])
UPDATE
You can also try using validate_unique() in your form class:
def validate_unique(self, exclude=None):
qs = BlogPost.objects.all()
if qs.filter(chief_title=self.chief_title).exists():
raise ValidationError("Blog post with this title already exists")
You can raise a ValidationError as shown in the docs. This would then be displayed in the form for the user.
def clean_slug(self):
slug = slugify(self.cleaned_data.get("chief_title")) if len(self.cleaned_data.get("slug", )) == 0 \
else self.cleaned_data.get("slug", )
if BlogPost.objects.filter(slug=slug).exists():
raise ValidationError(_('Slug already exists.'), code='invalid')
return slug
If you set unique = True Django takes care of checking whether another entry already exists in the database and adds an error in ModelForm.errors.
This is happening inModelForm.validate_unique.
There is no need to bother with this method unless you want to add more info in the error, such as the url of the existing object (it will cost 1 db hit).
Otherwise, the error already exists and you can just return the form with its errors instead of returning a new instance of the form as you currently do in views.py.
Therefore the following views.py as explained in this post should do what you are trying to do:
app/views.py:
def create_blog_view(request):
context = {}
# ...
if request.method == 'POST':
form = CreateBlogPostForm(request.POST or None, request.FILES or None)
if form.is_valid():
# do your thing
else:
context['form'] = form
return render(request, "HomeFeed/create_blog.html", context) # context instead of {}
If you want to get fancier and hit the db once more, you can add the url of the existing object in the errors list as such:
project/settings.py
...
# If you want to provide more info
MY_UNIQUE_BLOGPOST_ERROR_MESSAGE = "This BlogPost already exists"
...
app/models.py
from django.db import models
from django.conf import settings
from django.urls import reverse
class BlogPost(models.Model):
# ...
slug = models.SlugField(
blank=True,
unique=True,
error_messages={"unique": settings.MY_UNIQUE_BLOGPOST_ERROR_MESSAGE),
)
def get_admin_url(self):
return reverse("admin:app_blogpost_change", args=(self.id,))
...
app/forms.py
from django import forms
from django.conf import settings
from django.utils.html import format_html
from itertools import chain
from app.models import BlogPost
class CreateBlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = '__all__'
def validate_unique(self):
'''
If unique error exists, find the relevant object and return its url.
1 db hit
There is no other need to override this method.
'''
super().validate_unique()
if settings.SHOP_ENTITY_UNIQUE_ERROR in chain.from_iterable(
self.errors.values()
):
instance = BlogPost.objects.get(slug=self.instance.slug)
admin_url = instance.get_admin_url()
instance_name = instance.__str__()
self.add_error(
None,
forms.ValidationError(
format_html(
"This entry already exists: {1}",
admin_url,
instance_name,
),
code="unique",
),
)

how can i display other user information in django

I'm creating a website that the user can look at other users profile but the problem is when the user enter another user profile it show his personal information
this is the urls.py file code
urlpatterns = [
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
]
this is the view.py file code
class UserPostListView(ListView):
model = Post = Profile
template_name = 'website/user_posts.html'
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user)
def get_username_field(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Profile.objects.filter(user=user)
this is the models.py file
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.IntegerField(verbose_name='Ålder', default=15,
blank=True)
def get_absolute_url(self):
return reverse('user_posts', kwargs={'pk': self.pk})
def __str__(self):
return f'{self.user.username} Profile'
user_posts.html file
{{ user.get_full_name }}
{{ user.profile.age }}
{{ view.kwargs.username }}
in the template it's show the username but it didnt' show the name and the age.
user is always the current logged-in user. Your view uses the Profile model, so you can either access profile or object.
{{ profile.user.get_full_name }}
{{ profile.age }}
Note, your get_username_field method is never called and does not do anything; you should remove it.
Note also, it's really not a good idea to store age as an integer in the database. That means you somehow have to update it every year, as people have a strange habit of getting older... Better to store the date of birth, and have a method to display the age.
First of all your get_username_field is of no use.
In your views.py,
class UserPostListView(ListView):
model = Profile
template_name = 'website/user_posts.html'
context_object_name = 'user_content'
allow_empty = False #this will show 404 if the username does not exists
def get_queryset(self):
return User.objects.filter(username=self.kwargs['username'])
# you can do it in one line now
Now to show this in html,
{% for user in user_content %}
{{user.get_full_name}}
# rest of your code
{% endfor %}
You can also show posts of that particular user in same way as above.

Django ImageField won't upload in function based view, but it does in the admin

I've been trying to add some user uploaded profile picture to my website. It works fine when I do it from the admin, the image is showed and all the engines seems to be working fine (image going to the correct upload location and so on). The problem is when I try to do the same thing from my view.
I noticed that the print("upload_location") only appears when I do it from the admin. The weird thing is that all the other fields in my Profile model are working fine (like name "foo" is updated to "foobar") and not only in the admin, but in the view as well. The issue is only with the ImageField.
I believe it could have something to do with the way I'm handling the form.is_valid(), but I've been playing around with that and nothing changed (I know it is working to some extend, since HttpResponseRedirect is working.
Any ideas?
views.py
...
#login_required
def profile_update(request, username=None):
obj = get_object_or_404(User, username=username)
user = obj.profile
form = ProfileForm(request.POST or None, instance = user)
context = {
"form": form
}
if form.is_valid():
form.save()
return HttpResponseRedirect('/profiles/{username}'.format(username=user.user))
template = 'profile_update.html'
return render(request, template, context)
forms.py
from django import forms
from .models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = [
"profilePic",
"nome",
...
]
def profile(self, request, user):
print('printing forms')
user.uf = self.cleaned_data['uf']
user.cidade = self.cleaned_data['cidade']
user.telefone = self.cleaned_data['telefone']
user.save()
models.py
...
User = settings.AUTH_USER_MODEL # 'auth.User'
def upload_location(instance, filename):
print("upload_location")
return "%s/%s" %(instance.user, filename)
class Profile(models.Model):
user = models.OneToOneField(User)
id = models.AutoField(primary_key=True)
width = models.IntegerField(default=0, null=True, blank=True,)
height = models.IntegerField(default=0, null=True, blank=True,)
profilePic = models.ImageField(
upload_to = upload_location,
blank=True, null=True,
verbose_name = 'Foto de Perfil',
width_field="width",
height_field="height",
)
...
template.html
...
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy }}
<input type="submit" value="Enviar" class="btn btn-primary"/>
</form>
...
You need to add FILES into the form.
form = ProfileForm(request.POST or None, request.FILES or None, instance = user)
Docs: https://docs.djangoproject.com/en/1.10/topics/http/file-uploads/

How to do custom signup with django-allauth?

I'm trying to ask a user some additional info while signing up. I'm using django allauth for authorization and authentication. I try to add three more fields during the signup process. If If I run it, it shows me the standard form plus gender field. However, it doesn't seem to really work. How can I save the data? Could someone help? Thank you in advance!
EDITED: if I just use
if form.is_valid():
form.save()
return redirect('/success/')
I get an error:
save() missing 1 required positional argument: 'user'
I'm quite new to django.
I created signups app in the project.
I put this in allauth_settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'signups.forms.MySignupForm'
My signups/model.py:
from django.contrib.auth.models import User
from django.db import models
from allauth.account.models import EmailAddress
from allauth.socialaccount.models import SocialAccount
import hashlib
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
about_me = models.TextField(null=True, blank=True)
timestamp = models.DateTimeField(auto_now_add= True, auto_now=False)
updated = models.DateTimeField(auto_now_add= False, auto_now=True)
GENDER_CHOICES = (
('m', 'Male'),
('f', 'Female'),
)
# gender can take only one of the GENDER_CHOICES options
gender = models.CharField(max_length=1, choices=GENDER_CHOICES,
verbose_name='Gender')
def __unicode__(self):
return self.user.username
class Meta:
db_table = 'user_profile'
def profile_image_url(self):
"""
Return the URL for the user's Facebook icon if the user is logged in via
Facebook, otherwise return the user's Gravatar URL
"""
fb_uid = SocialAccount.objects.filter(user_id=self.user.id, provider='facebook')
if len(fb_uid):
return "http://graph.facebook.com/{}/picture?width=40&height=40".format(fb_uid[0].uid)
return "http://www.gravatar.com/avatar/{}?s=40".format(hashlib.md5(self.user.email).hexdigest())
def account_verified(self):
"""
If the user is logged in and has verified hisser email address, return True,
otherwise return False
"""
if self.user.is_authenticated:
result = EmailAddress.objects.filter(email=self.user.email)
if len(result):
return result[0].verified
return False
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
my signups/forms.py:
from allauth.account.forms import SignupForm
from django import forms
from .models import UserProfile
class MySignupForm(SignupForm):
class Meta:
model = UserProfile
gender = forms.CharField(max_length=1, label='gender')
def save(self, user):
user.gender = self.cleaned_data['gender']
user.save()
my signups/views.py:
from django.template import RequestContext
from django.shortcuts import render_to_response
from .forms import SignupForm
def index(request):
form = MySignupForm(request.POST or None)
if form.is_valid:
???
return render_to_response("signups/index.html", locals(),
context_instance=RequestContext(request))
My index.html is very basic, I just wanted to see the representation of the form:
{% extends 'account/base.html' %}
{% block head_title %}ProjectName{% endblock %}
{% block content %}
<form method="POST" action="">
{{ form.as_p }}
<input type="submit">
</form>
{% endblock %}
You are instantiating the SignupForm, which is the standard form but not your MySignupForm in the view. Change it like this:
def index(request):
form = MySignupForm()
return render_to_response("signups/index.html", locals(),
context_instance=RequestContext(request))