Django form to display user attributes - python

I'm having an odd problem that I can't remedy: Django is not displaying any of the user's attributes except for the username when using a form that includes a foreign key attribute between two models.
The associated username is showing up correctly, but none of the other attributes are (first name, last name, email, etc).
Similarly, the debugging print statement that I've placed in the views.py is correctly printing the user's attributes in the terminal output.
Why aren't any of the user's attributes showing up in the html template?
models.py
class UnitGroup(models.Model):
unit_name = models.CharField(max_length=150, verbose_name='Unit Name')
class UnitUser(models.Model):
user = models.OneToOneField(User)
unit = models.ForeignKey(UnitGroup)
ROLES = ((0, 'Admin'), (1, 'Member'))
role = models.IntegerField(null=True, blank=True, verbose_name='Role', choices=ROLES)
def __string__(self):
return self.user.first_name
forms.py
class UserGroupForm(forms.ModelForm):
class Meta:
model = UnitUser
fields = '__all__'
views.py
from units.forms import UnitForm, UnitDetailsForm, UserGroupForm
from units.models import UnitGroup, UnitDetails, UnitUser
from django.contrib.auth.models import User
def edit_members(request):
if request.user.is_authenticated() \
and request.method == 'GET' \
and 'unit' in request.GET:
unit_group = request.GET['unit']
unit_users = UnitUser.objects.filter(unit=unit_group)
unit_forms = []
for i in unit_users:
# debug
print(i.user.first_name)
print(i.user.last_name)
unit_forms.append(UserGroupForm(instance=i))
return render(request, 'units/edit-members.html', {'unit_forms': unit_forms})
edit-members.html
{% for form in unit_forms %}
user: {{ form.user }} <br>
first name: {{ form.user.first_name }}
{% endfor %}

Your UserGroupForm is a model-form, based on the UnitUser model.
The fields of the form are based on the fields of the model.
Since UnitUser doesn't contain details about the user, but merely a single reference to the user model, the user model itself is only represented via a single field. The string representation of a user model is the username. I would think that's the reason you see the username in your form.
In short: Your model-form considers the user just as a single field and will use the string representation of the value of that field as init-value.
If you want to display further attributes of the user in your form, you might have to construct a specific form for that purpose.

Related

Can't display foreign key related object in Django DetailView

I've got a booking class which is related to Customer and Barber models. I can display all bookings using the detail view, however, can't figure out how to display a booking/bookings that a specific barber has. Basically, I want to get a booking or multiple bookings of a barber based on the ID given to the url.
Here is my model:
customer_id = models.ForeignKey(User, on_delete = models.CASCADE,)
barber_id = models.ForeignKey(Barber, on_delete = models.CASCADE)
timeslot = models.DateTimeField('appointment')
def __str__(self):
return f"{self.customer_id} {self.barber_id} {self.timeslot}"
def get_absolute_url(self):
return reverse("model_detail", kwargs={"pk": self.pk})
My view:
class GetBarberBooking(DetailView):
model = Booking
template_name = "barber_booking.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['barber'] = Booking.objects.filter(
id=self.kwargs.get('<str:pk'))
return context
My url path:
path('barber-booking/<str:pk>/', views.GetBarberBooking.as_view(), name='barber-booking'),
You can remove this piece of code:
context['barber'] = Booking.objects.filter(
id=self.kwargs.get('<str:pk'))
And in template just use:
{{ object.barber_id }}
And show all the booking for barber:
{{ object.barber_id.booking_set.all }}
It will show all the the barber. This works because of FK relation between Booking and Barber model. More information can be found in Django documentation. For reverse relation (Many to one), please check this documentation.
FYI, you do not need to create a field name suffix with _id, because Django creates that automatically for you.
Also, if you want to query a Barber, then you should use Barber as the model in the DetailView. Then you can use a similar query mentioned above:
# view
class GetBarberBooking(DetailView):
model = Barber
# template
{{ object.booking_set.all }}

Django: How to retrieve the logged in user's details

I am in the process of learning Django. I am trying to create a simple directory web app. I am able to print out all the users details for the main directory page. However, I want to add a feature that when a person logs into the directory app they are brought to their 'profile' page where they will be able to see all their own details e.g. business name, profile pic.
I know how to retrieve the default fields e.g. username and email. But cannot seem to retrieve the custom fields that I declared myself. Here is my attempts so far...
Models.py:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserProfileInfo(models.Model):
user = models.OneToOneField(User,on_delete=models.DO_NOTHING)
#additional classes
business_name = models.CharField(max_length=191,blank=True)
trade = models.CharField(max_length=191,blank=True)
portfolio_site = models.URLField(blank=True)
profile_pic = models.ImageField(upload_to='profile_pics',blank=True)
def __str__(self):
return self.user.username
Views.py:
#login_required
def profile(request):
profile = UserProfileInfo.objects.filter(user=request.user)
context = { 'profile': profile }
return render(request, 'dir_app/profile.html',context)
Profile.html:
<div class="container-fluid text-center">
{% for p in profile %}
<h3>{{p.business_name}}</h3>
{% endfor %}
</div>
Since UserProfileInfo is related to User via OneToOneField, you can have one UserProfileInfo per User. So, instead of Filter, you can simply get your desired UserProfileInfo object through your current (logged in) User as follows.
views.py,
profile = UserProfileInfo.objects.get(user=request.user)
Also, before you can get a request.user object, you have to make sure that your user is authenticated and logged in. Otherwise, you might get None in place of a User object and therefore, no associated UserProfileInfo.
Since it is a OneToOneField there is only one Profile object for a User, you thus can obtain this with:
#login_required
def profile(request):
profile = request.user.userprofileinfo
return render(request, 'my_template.html',{'profile': profile})
Then in the template, you render it with:
{{ profile.business_name }}
you can use it directly on template without sending it f:
{{request.user.userprofile}}

django 2 - User and userprofile models, how to obtain all fields in a single query?

Update and solution below.
I've been looking for a solution but I'm not finding anything that sticks out.
I've created a Profile model which is linked to the standard User model via one-to-one field which is working in admin. I want to pull all fields/data for both models into a single queryset. I'm trying to create a user editing form and I want to pull in all fields for User and Profile based on the current logged in user and display those fields which I will have a page to edit and save those fields.
What are the best options to achieve this, simple is better.
class Profile(models.Model):
address = models.blablabla
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
def profile_edit(request):
form = UserProfileForm(request.POST or None)
instance = Profile.objects.all().filter(user__username=request.user).values # This is the place I need to create a single queryset.
if request.method == "POST" and form.is_valid():
form = UserProfileForm(request.POST, instance=instance)
user_form = form.save()
print("POST event")
else:
form = UserProfileForm(instance=instance)
print(form)
return render(request, 'frontend/profile_edit.html', {'form': form})
I'm manually creating the forms in the template so I would like to have something like {{ form.username }} {{ form.profile.address }} or something like that. I'm likely doing things poorly, I'm new to django.
UPDATE
Complete solution
Complete steps to gain access to user and profile models in code and template.
I decided not to replace the User model with my own in case I missed out on features provided by django. It also seemed to complicate things that might hurt later on. So I've gone with the separate UserProfile model and attached it to the User model. Here is what I did for future readers.
models.py
from django.db.models.signals import post_save
class UserProfile(models.Model):
#take note of the related_name='profile' this is used to reference the fields in code and template.
#Each field of type 'text' I added default='' at the end, I got an error when it was int based so I removed the flag for that field. I read this might cause an error when you try and auto-create the profile, see what works for you and you might not want it.
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
country = models.CharField(max_length=2, blank=True, null=True, default='')
...
# Auto-create the profile upon user account creation. It's important to start with a fresh database so the user and profile ID's match.
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
# Create your models here.
#In code, you can access your user and profile field data like so.
request.user.profile.fieldname
request.user.fieldname
In template you can do the same
{{ user.fieldname }}
{{ user.profile.fieldname }}
There's no need for a query here at all - you don't want a queryset, you want a single instance.
In this case, request.user.profile will give you the entire Profile object related to the current user.

How can I specify form validation errors when they occur?

I'm quite new to Django Forms, and I'm facing a problem that I cannot solve. I've been googling and reading the docs, but I can't find the place where this is explained. My problem is that I have an Animal Model and a ModelForm:
class Animal(models.Model):
name = models.CharField(max_length=300)
age = models.PositiveSmallIntegerField()
race = models.ForeignKey(Race)
description = models.TextField()
state = models.ForeignKey(State)
pub_date = models.DateTimeField(auto_now_add=True)
adoption_limit = models.DateTimeField(blank=True, null=True)
location = models.ForeignKey(Location)
publisher = models.ForeignKey(User)
def __unicode__(self):
return self.name
class AnimalForm(ModelForm):
class Meta:
model = Animal
I render this info via urls.py, calling this view:
#login_required
def new_animal(request):
if request.method == "POST":
form = AnimalForm(request.POST)
if form.is_valid():
form.save()
return render_to_response('/')
else:
variables = RequestContext(request, {'e': form.errors})
return render_to_response('web/error.html', variables)
else:
form = AnimalForm()
variables = RequestContext(request, {'form': form})
return render_to_response('web/animal_form.html', variables)
It seems that I have an error introducing the adoption_limit field, so the data does not get saved in DB. This is because I just set a date and not a time into the text field displayed by the form.
I would like to know how can I do two things:
How can I send the error message to the form again, so that I can add a text next to the field that I have not set correctly? I.e., like the admin does.
How can I put the same input type for DateTimeField that I have in the admin interface? (with the Today and Now functions)
The way you have written your view, to display form errors, in your web/error.html template, simply output the errors:
{%if e %}
You had some errors in your submission!<br />
{{ e }}
{% endif %}
However, you don't have explicitly pass the errors list, it is part of the form itself. A bit of simplification:
variables = RequestContext(request, {'form': form})
return render_to_response('web/error.html', variables)
Then, in your template:
{% if form.errors %}
You have some errors!<br />
{{ form.errors }}
{% endif %}
For the second part of your question - to display the django date time widget - things get a bit more involved:
# First, you need to import the widget:
from django.contrib.admin.widgets import AdminSplitDateTime
from django.forms import TextField
# In your form class, you have to specify the widget
# for the field.
class AnimalForm(forms.ModelForm):
pub_date = models.TextField(widget=AdminSplitDateTime)
class Meta:
model = Animal
In order for this to work though, you have to make sure your admin media directory is accessible from your project (since all the javascript and css is included there). You'll also to have make sure that all the stylesheets are also added. It is much easier (and simpler) to use your own javascript form widget from your preferred library.
Finally, as stated in the documentation, if you override any fields, you need to add all the other validation logic yourself:
If you explicitly instantiate a form field like this, Django assumes
that you want to completely define its behavior; therefore, default
attributes (such as max_length or required) are not drawn from the
corresponding model. If you want to maintain the behavior specified in
the model, you must set the relevant arguments explicitly when
declaring the form field.
burhan's answer is spot on. Additionaly, You might probably want to hide the publisher on the form and deal with it in your view.
To do this, add exclude = ('publisher',) to class Meta in your ModelForm.
And then in your view:
if form.is_valid():
animal = form.save(commit=false)
animal.publisher = request.user
animal.save()
Otherwise, as it stands I think your form will show all users in a dropdown, which might not be what you want.

Foreign key to User table in django

I'm using django's built-in contrib.auth module and have setup a foreign key relationship to a User for when a 'post' is added:
class Post(models.Model):
owner = models.ForeignKey('User')
# ... etc.
Now when it comes to actually adding the Post, I'm not sure what to supply in the owner field before calling save(). I expected something like an id in user's session but I noticed User does not have a user_id or id attribute. What data is it that I should be pulling from the user's authenticated session to populate the owner field with? I've tried to see what's going on in the database table but not too clued up on the sqlite setup yet.
Thanks for any help...
You want to provide a "User" object. I.e. the same kind of thing you'd get from User.objects.get(pk=13).
If you're using the authentication components of Django, the user is also attached to the request object, and you can use it directly from within your view code:
request.user
If the user isn't authenticated, then Django will return an instance of django.contrib.auth.models.AnonymousUser. (per http://docs.djangoproject.com/en/dev/ref/request-response/#attributes)
Requirements --> Django 3, python 3
1) For add username to owner = models.ForeignKey('User') for save that, in the first step you must add from django.conf import settings above models.py and edit owner = models.ForeignKey('User') to this sample:
class Post(models.Model):
slug = models.SlugField(max_length=100, unique=True, null=True, allow_unicode=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, default=1, on_delete=models.CASCADE)
2) And for show detail Post, special owner name or family or username under the post, you must add the following code in the second step in views.py:
from django.shortcuts import get_object_or_404
.
.
.
def detailPost(request,slug=None):
instance = get_object_or_404(Post, slug=slug)
context = {
'instance': instance,
}
return render(request, template_name='detail_post.html', context=context)
3) And in the third step, you must add the following code for show user information like user full name that creates a post:
<p class="font-small grey-text">Auther: {{ instance.owner.get_full_name }} </p>
now if you want to use user name, you can use {{ instance.owner.get_username }}
or if you want to access short name, you can use {{ instance.owner.get_short_name }}.
See this link for more information.

Categories