I'm trying to follow this post to associate a profile picture to a user in Django.
I have the following model
class MyUser(AbstractBaseUser):
"""
Custom user class.
"""
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
email = models.EmailField('email address', unique=True, db_index=True)
is_staff = models.BooleanField('is staff', default=False)
first_name = models.TextField('first name', default=None, null=True)
last_name = models.TextField('last name', default=None, null=True)
date_of_birth = models.DateField('date of birth', null=True)
avatar = models.ImageField('profile picture', upload_to='static/media/images/avatars/', null=True, blank=True)
has_picture = models.BooleanField('has profile picture', default=False)
adult = models.BooleanField('is adult', default=False)
gender = models.CharField('gender', max_length=1, choices=GENDER_CHOICES)
objects = MyUserManager()
REQUIRED_FIELDS = ['date_of_birth', 'gender']
USERNAME_FIELD = 'email'
# Insert a lot of methods here
def set_avatar(self):
self.has_picture = True
I used the form from the post, but added this to my save() for the ChangeForm:
def save(self, commit=True):
user = super(MyChangeForm, self).save(commit=False)
if user.avatar: # If the form includes an avatar
user.set_avatar() # Use this bool to check in templates
if commit:
user.save()
return user
The logic behind this is to add a picture then set a bool flag to tell the template whether to display a generic "blank user" avatar if there's no picture associated with the profile and to display a thumbnail if there's an avatar attribute within the user.
From my form, neither the uploading nor the has_picture fields get set. Within admin, however, I can upload photos.
What am I doing wrong?
It's not good idea to set a Boolean for checking whether the user has an avatar. You have two options: you can play with the empty url in your template or define a method to set user avatar in models.py
Option 1: in your template
{% if user.avatar == None %}
<img src="DEFAULT_IMAGE" />
{% else %}
<img src="user.avatar"/>
{% endif %}
Option 2: in your models
def set_avatar(self):
_avatar = self.avatar
if not _avatar:
self.avatar="path/to/default/avatar.png"
Also, if your user never gets saved, if because you are calling save method with commit=False.
Related
I am building a web-application for my senior design project with Python and Django. I have a user model that is able to read/write articles to display on the website. I have some tasks I want to accomplish.
I want to make it so that if an article is accessed (read) by a user, it is indicated for only that user that the article has been previously accessed. If I were to log into a brand new user account, the same article wouldn't be indicated as "accessed" for the new account.
How would I be able to present on the front-end side that the article has been viewed by the user logged in? (ie: make the article title bolded or a different color to indicate its been already visited)
Below are my models and views:
User model
class User(AbstractBaseUser, PermissionsMixin):
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
email = models.EmailField(unique=True, max_length=255, blank=False)
university = models.CharField(max_length=200, null=True, blank=True)
newsletter_subscriber = models.BooleanField(default=False)
is_email_verified = models.BooleanField(default=False)
is_staff = models.BooleanField(
default=False,
help_text=(
'Designates whether the user can log into '
'this admin site.'
),
)
is_active = models.BooleanField(
default=True,
help_text=(
'Designates whether this user should be '
'treated as active. Unselect this instead '
'of deleting accounts.'
),
)
date_joined = models.DateTimeField(default=timezone.now)
objects = UserManager()
USERNAME_FIELD = 'email'
def __str__(self):
return self.email
Article model
class Article(models.Model):
user = models.ForeignKey(
User, on_delete=models.DO_NOTHING, null=True, blank=True)
title = models.CharField(max_length=200, null=True, blank=False)
author = models.CharField(max_length=200, null=True, blank=False)
year = models.CharField(max_length=200, null=True, blank=False)
journal = models.CharField(max_length=200, null=True, blank=False)
description = models.TextField(null=True, blank=True)
URL = models.CharField(max_length=200, null=True, blank=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class MetaData:
ordering = ['-created']
Article detail view
class ArticleDetail(LoginRequiredMixin, DetailView):
model = Article
context_object_name = 'articles'
template_name = 'home/article_detail.html'
Thank you!
You could create an extra table.
class ArticleSeenRecord(models.Model):
user = models.ForeignKey("django.contrib.auth.models.User", on_delete=models.CASCADE)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
And then in your article view, create a new record when one doesn't exist, for that article combined with the authenticated user.
class ArticleDetail(LoginRequiredMixin, DetailView):
model = Article
context_object_name = 'articles'
template_name = 'home/article_detail.html'
def get_object(self, queryset=None):
obj = super().get_object(queryset)
record, created = ArticleSeenRecord.objects.get_or_create(user=self.request.user, article=obj)
return obj
class Article(models.Model):
...
def seen_by_user(self, user):
return self.atricleseenrecord_set.objects.filter(user=user).exists()
I added the extra function here. You will also need to add a template tag which you can ideally copy from this example
#register.simple_tag
def article_seen_by_user(article, user):
return article.seen_by_user(user)
For further guidance on how to use and register custom template tags, please refer to this page of the documentation:
https://docs.djangoproject.com/en/4.0/howto/custom-template-tags/
Specifically this section:
https://docs.djangoproject.com/en/4.0/howto/custom-template-tags/#django.template.Library.simple_tag
I am new to Django I have two models are user and address, here user having two
foreign key fields are 'localaddress', 'permanentaddress'
Address model:
class Address(models.Model):
fulladdress = models.CharField(max_length=1000, null=True, blank=True)
additional_address = models.CharField(max_length=1000, null=True, blank=True)
street_address = models.CharField(max_length=150, null=True, blank=True)
route = models.CharField(max_length=150, null=True, blank=True)
city = models.CharField(max_length=100, null=True, blank=True)
state = models.CharField(max_length=100, null=True, blank=True)
country = models.CharField(max_length=100, null=True, blank=True)
pincode = models.IntegerField(null=True, blank=True)
class Meta:
db_table = 'address'
User model:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
localaddress = models.ForeignKey(Address, on_delete=models.CASCADE, related_name="localaddress", null=True, blank=True)
permanentaddress = models.ForeignKey(Address, on_delete=models.CASCADE, related_name="permanentaddress", null=True, blank=True)
class Meta:
db_table = 'user'
settings.py:
AUTH_USER_MODEL = 'student.User'# changes built-in user model to ours
here both localaddress and permanentaddress having same foreign key (Address model only)
Edit form:
forms.py:
class LocaladdressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['fulladdress', 'additional_address', 'street_address', 'route', 'city', 'state', 'country', 'pincode']
def save(self, commit=True):
user = super(LocaladdressForm, self).save(commit=False)
user.fulladdress = self.cleaned_data['fulladdress']
user.additional_address = self.cleaned_data['additional_address']
user.street_address = self.cleaned_data['street_address']
user.route = self.cleaned_data['route']
user.city = self.cleaned_data['city']
user.state = self.cleaned_data['state']
user.pincode = self.cleaned_data['pincode']
user.country = self.cleaned_data['country']
if commit:
user.save()
return user
views.py:
def address_form(request):
if request.method == 'POST':
address = Address()
form = AddressForm(request.POST, instance=address)
if form.is_valid():
form.save()
userid = request.user.id
User.objects.filter(pk=userid).update(localaddress=address)
return redirect(reverse('student:view_profile'))
else:
args = {'form': form}
return render(request, 'student/addressform.html', args)
else:
form = AddressForm()
args = {'form': form}
return render(request, 'student/addressform.html', args)
i am using for loop to render form elements, i don't know where i did wrong
addressform.html:
<form method="post">
{% csrf_token %}
{{ form.fulladdress }}
{{ form.additional_address}}
{{ form.street_address}}
{{ form.street_address }}
{{ form.city }}
{{ form.state }}
{{ form.pincode }}
{{ form.country }}
</form>
Here i need to render one form localaddress and another form permanentaddress foreign key fields to my template. Initially i am trying first form (localaddress) please help me any one.
Thanks in advance ...
Localaddress form screenshot: Here i did it Autocomplete Address Form using google address api reference link here Autocomplete Address
I am able to persist address and user object but user object creating new object it is not persisting existing object (means localaddress)
I can answer partially about a concept in Django.
Whenever you create a relationship between two Django models you decide which model will be the main model. Over here you have User model and Address model. I am sure in most cases you will agree that User model is the main model and Address will be the sub-model or child model or whatever you call it.
So your ForeignKey field should ALWAYS be on the sub-model. So instead of using ForeignKey on User model like you have done you should do something like this:
address_type_choices = [
(1, 'Local'),
(2, 'Permanent'),
]
class Address(models.Model):
user = models.ForeignKey(User)
type = models.IntegerField(default=1, choices=address_type_choices)
house = model.CharField(max_length=50)
road = model.CharField(max_length=50)
area = model.CharField(max_length=50)
...
Please change the structure of your model and edit the question. I am sure that is what others will suggest too.
Remember, main model will not have foreign key field.
Update: Changed the model to allow you to maintain both local and permanent address.
I am trying to make a user panel in which each user's profile info (like avatar, joined date, etc.) are being displayed along with their posts. Here is the view that render the threads:
def topic(request, topic_id):
"""Listing of posts in a thread."""
posts = Post.objects.select_related('creator') \
.filter(topic=topic_id).order_by("created")
posts = mk_paginator(request, posts, DJANGO_SIMPLE_FORUM_REPLIES_PER_PAGE)
topic = Topic.objects.get(pk=topic_id)
topic.visits += 1
topic.save()
return render_to_response("myforum/topic.html", add_csrf(request, posts=posts, pk=topic_id,
topic=topic), context_instance=RequestContext(request))
The Topic model is:
class Topic(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(max_length=10000, null=True)
forum = models.ForeignKey(Forum)
created = models.DateTimeField()
creator = models.ForeignKey(User, blank=True, null=True)
visits = models.IntegerField(default = 0)
And the UserProfile model:
class UserProfile(models.Model):
username = models.OneToOneField(User)
name = models.CharField(max_length=30, blank=True)
city = models.CharField(max_length=30, blank=True)
country = models.CharField(
max_length=20, choices= COUTNRY_CHOICES, blank=True)
avatar = ImageWithThumbsField(), upload_to='images', sizes=((32,32),(150,150),(200,200)), blank=True)
created_at = models.DateTimeField(auto_now_add=True, blank=True)
updated_at = models.DateTimeField(auto_now=True, blank=True)
The problem is how best to join these two tables so that userprofile fields can be displayed in topic.html along with username?
Add them to context since you already have a database relation Users and Topics.
# add this to context
topic = Topic.objects.get(pk=topic_id)
creator = topic.creator.get().profile # This pulls the user object from creator field
context['creator'] = creator # Add to context
Now you can use the 'creator' context to pull fields
<h1>{{ creator.name }}</h1>
as for the avatar, if you have your media root set in settings you simply use an
<img src="/media/images/{{ creator.avatar }}">
Oh and also you can save alot of time by using ListView and DetailView part of Django's class based views.
Sorry forgot to mention you should add a related name to your one to one,
username = OneToOneField(User, related_name='profile')
I have a view which validates data from a form which just has some basic information about an item. I'm confused with how the is_valid method works here even after reading
this . If the user doesn't input some of the required fields like name or image 1, I want them to see the error on the page "this field is required" or something of that nature. I thought if the form.is_valid returned False, these messages would automatically be raised on the page for the user. Or do I need to specify what error message for each field somewhere that I would want the user see?
#view
def sell(request):
if request.method == "POST":
form = AddItem(request.POST, request.FILES)
if form.is_valid():
item = form.save(commit=False)
item.user = request.user
item.is_active = True
item.slug = slugify(item.name)
item.save()
return HttpResponseRedirect('thanks.html')
else:
form = AddItem()
return render_to_response('forsale.html', locals(), context_instance=RequestContext(request))
#form
class AddItem(forms.ModelForm):
name = forms.CharField(label="Title")
class Meta:
model = Item
exclude = ('user','slug','is_active',)
#model
class Item(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=75)
slug = models.SlugField(max_length=50, unique=True)
is_active = models.BooleanField(default=True, blank=True)
image1 = models.ImageField(upload_to='img')
image2 = models.ImageField(upload_to='img', blank=True)
image3 = models.ImageField(upload_to='img', blank=True)
image_caption1 = models.CharField(max_length=200, blank=True)
image_caption2 = models.CharField(max_length=200, blank=True)
image_caption3 = models.CharField(max_length=200, blank=True)
price = models.DecimalField(max_digits=8, decimal_places=2)
quantity = models.IntegerField(default=1)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
shipping_price = models.DecimalField(decimal_places=2, max_digits=6)
categories = models.ManyToManyField(Category)
You need to extract the errors from the form object using form.errors then deal with the dict however you want. If you're using ajax, simply send the dict as json back over and use javascript to handle it. If it was a direct html form submit, then you need to render and respond with a page with the errors in the passed dictionary and deal with the passed error in the template (usually with an {% if errors %} tag
What i am trying to achieve is that i want to extend the profile model further to either teacher or student. In the signup form I added a choice field where user select whether he is teacher or student. Below is my model structure.
class Profile(UserenaLanguageBaseProfile):
""" Default profile """
GENDER_CHOICES = (
(1, _('Male')),
(2, _('Female')),
)
user = models.OneToOneField(User,
unique=True,
verbose_name=_('user'),
related_name='profile')
gender = models.PositiveSmallIntegerField(_('gender'),
choices=GENDER_CHOICES,
blank=True,
null=True)
class Teacher(Profile):
profile = models.OneToOneField(Profile,
unique=True,
verbose_name=_('profile'),
related_name='teacher')
home_address = models.CharField(_('home_address'), max_length=255, blank=True)
home_phone = models.CharField(_('home_phone'), max_length=30, blank=True)
cell_phone = models.CharField(_('cell_phone'), max_length=30, blank=True)
experience = models.IntegerField(default = 0)
summary = models.TextField(_('summary'), max_length=500, blank=True)
class Student(Profile):
profile = models.OneToOneField(Profile,
unique=True,
verbose_name=_('profile'),
related_name='student')
grade = models.CharField(_('grade'), max_length=50, blank=True)
I am overriding the signup save method as:
def save(self):
new_user = super(SignupFormExtra, self).save()
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
if self.cleaned_data['teacher_or_student'] == 'teacher':
teacher = Teacher(profile = new_user.get_profile())
teacher.save()
elif self.cleaned_data['teacher_or_student'] == 'student':
student = Student(profile = new_user.get_profile())
student.save()
return new_user
When teacher.save() or student.save() method is called it raises an integrity error that "(1048, "Column 'user_id' cannot be null")" but i am not creating a new user instance here i am trying to assign the newly created profile_id to teacher or student model. I am doing in the wrong way?? what should I do?
As the error says you can't create a Student or Teacher without user as you've defined it as a non nullable field.
Make sure you're passing your class the new_user you've defined..
# ...
new_user.save()
if self.cleaned_data['teacher_or_student'] == 'teacher':
teacher = Teacher(profile = new_user.get_profile(), user=new_user)
teacher.save()
elif self.cleaned_data['teacher_or_student'] == 'student':
student = Student(profile = new_user.get_profile(), user=new_user)
student.save()
I might be wrong, but why do you subclass your models from Profile model (so you have a "user" field in it already), and right after you have a "profile" OneToOneField field for Profile model again?