I have a user profile page connected to a model which amongst other fields contain the following:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
That works like it should; the profile image connected to the user in question is loaded, and the distinction between users is made.
What I'm trying to do now is connect a separate gallery model to the profile page, that the users may have a small image gallery to goof around with.
The gallery model looks like this:
class GalleryModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
img_1 = models.ImageField(default='default.jpg', upload_to='images')
img_2 = models.ImageField(default='default.jpg', upload_to='images')
img_3 = models.ImageField(default='default.jpg', upload_to='images')
The views.py file looks like this:
class ProfileDetailView(DetailView):
model = Profile # Is something iffy here? Should this refer to the GalleryModel as well?
template_name = 'account/view_profile.html'
def get_object(self):
username = self.kwargs.get('username')
if username is None:
raise Http404
return get_object_or_404(User, username__iexact=username, is_active=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
username = self.object.username
context['person'] = GalleryModel.objects.get(user__username=username) #loads username string
context['img_1'] = GalleryModel.objects.last().img_1
context['img_2'] = GalleryModel.objects.last().img_2
context['img_3'] = GalleryModel.objects.last().img_3
return context
I've tried a bunch of ideas (i.e. various approaches to the filter() and get() methods) and scrutinizing https://docs.djangoproject.com/en/2.1/topics/db/queries/ and sifting through what I could find on SO, but I can't work it out.
For instance filter(username__iexact=username) doesn't seem to do the trick, nor do variations upon the theme produce anything but error messages, that I don't really understand.
I can get the username to go through if I insert {{ person }} in the template, but how do I get the objects (images) connected to the username in the GalleryModel?
Trying for the following is a no go:
GalleryModel.objects.get(user__username=username).img_1
As always, I have an eerie feeling that I'm missing something rather simple :)
NB!: I know that the last() method is not what I'm supposed to do, obviously, but thus far it's the only way I've managed to get images to render unto the template.
If you want to connect the Gallery to the Profile, you have to add Profile as ForeignKey, not User.
class GalleryModel(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
Except if you have another kind of Gallery, use Gallery(models.Model).
Related
Thanks in advance if you're reading this... I'm a High School student working on a web application using Django, to help students find internships, and facilitate parents posting internship offers -- a sort of marketplace if you will.
I'm trying to create a profile/account page for the users but I need a way to differentiate between whether the account logged in is a Student or Employer so that I can use views.py to generate a page appropriate to their account.
In models.py, I have two different profile types which can be associated with a user account (handled by django.contrib.auth), see below for reference.
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
profilePic = models.ImageField(default='default.jpg', upload_to='profile_pics')
class Meta:
verbose_name = 'Student Profile'
def __str__(self):
return f"{self.user.username}'s Profile"
class Employer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
profilePic = models.ImageField(default='default.jpg', upload_to='profile_pics')
company = models.CharField(max_length=100, default='Unspecified')
class Meta:
verbose_name = 'Employer/Parent Profile'
def __str__(self):
return f"{self.user.username}'s Profile"
In my views.py page, I'm trying to create a view for the account/profile that can detect whether the currently logged-in user's profile is linked to either the 'Student' or 'Parent' model and serve a page accordingly. I've tried a very rudimentary approach, as below, but unsurprisingly it's not working.
def account(request):
if user.student.username == True:
context = 'Account: Student'
return render(request, 'users/studentprofile.html', context)
elif user.employer.username == True:
context = 'Account: Employer'
return render(request, 'users/employer.html', context)
I was wondering if anyone had a suggestion as to how I can best accomplish this... apologies in advance is this approach is poorly structured or against the status-quo of Django Programming, I'm a complete beginner!
Thanks in advance all :)
You can actually check if the user has a student or employer attribute by using hasattr()
if hasattr(user, 'student'):
... # your logic
elif hasattr(user, 'employer'):
... # your logic
The following is completely optional, but what I personally like to do is to create some simple functions on my user model like in this example:
class User(models.Model):
...
def is_student(self):
return hasattr(self, 'student')
def is_employer(self):
return hasattr(self, 'employer')
And then, you can use those in the view:
if user.is_student():
... # your logic
elif user.is_employer():
... # your logic
By the way, do not apologize for being a beginner! This is a good question. I would suggest you to read the docs on One-to-one relationships.
Hope it helps!
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.
I am working on a webcomics platform, and I need to allow users to upload multiple images in one post.
Ideally - keep it as simple as possible, so that a person wouldn't have to refresh the page to upload each image, or create and save a post before adding images.
If a user could delete or reorder images it would be nice.
Also, I need to make it scalable, so that I wouldn't have problems with it later.
Can you give me advice on how to do it properly?
Should images have their own model, and be connected to the post with foreign key? (not sure if this makes sense, seems kinda ugly)
Or should I just keep a list of image urls in the some type of field on a post model, and then create some sort of cdn and upload it there?
Any advice is really appreciated.
If you're talking about scale then you need to figure out what the best practice is for serving images.
I would recommend that you use S3 to serve your static files (this will include your user-uploaded files too). Follow this tutorial which shows you how to set this up from scratch.
Now, digging into your schema, you will need both a Post and PostImage model:
models.py:
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=50)
body = models.TextField()
class PostImage(models.Model):
image = models.ImageField(
upload_to='<path_from_s3_tutorial>'
)
width_x = models.IntegerField()
width_y = models.IntegerField()
# Foreign Key to Post
post = models.ForeignKey('Post', null=True, blank=True)
Then when you will need to create a class in forms.py that uses an Inline Formset which allows you to upload multiple images when you create a post, as below:
>>> from myapp.models import Post, PostImage
>>> from django.forms import inlineformset_factory
>>>
>>> PostImageFormSet = inlineformset_factory(Post, PostImage, fields=('image',))
>>> post = Post.objects.get(id=13)
>>> formset = PostImageFormSet(instance=post)
You will then validate your formset in your view like so:
views.py
def create_post(request, post_id):
post = Post.objects.get(pk=post_id)
PostImageInlineFormSet = inlineformset_factory(Post, PostImage, fields=('image',))
if request.method == "POST":
formset = PostImageFormSet(request.POST, request.FILES, instance=post)
if formset.is_valid():
formset.save()
return redirect('index')
else:
formset = PostImageFormSet(instance=post)
return render(request, 'manage_books.html', {'formset': formset})
Note: Most of this example was modified from the one in the Django docs which I linked above.
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.
I have two models, Room and Image. Image is a generic model that can tack onto any other model. I want to give users a form to upload an image when they post information about a room. I've written code that works, but I'm afraid I've done it the hard way, and specifically in a way that violates DRY.
Was hoping someone who's a little more familiar with django forms could point out where I've gone wrong.
Update:
I've tried to clarify why I chose this design in comments to the current answers. To summarize:
I didn't simply put an ImageField on the Room model because I wanted more than one image associated with the Room model. I chose a generic Image model because I wanted to add images to several different models. The alternatives I considered were were multiple foreign keys on a single Image class, which seemed messy, or multiple Image classes, which I thought would clutter my schema. I didn't make this clear in my first post, so sorry about that.
Seeing as none of the answers so far has addressed how to make this a little more DRY I did come up with my own solution which was to add the upload path as a class attribute on the image model and reference that every time it's needed.
# Models
class Image(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
image = models.ImageField(_('Image'),
height_field='',
width_field='',
upload_to='uploads/images',
max_length=200)
class Room(models.Model):
name = models.CharField(max_length=50)
image_set = generic.GenericRelation('Image')
# The form
class AddRoomForm(forms.ModelForm):
image_1 = forms.ImageField()
class Meta:
model = Room
# The view
def handle_uploaded_file(f):
# DRY violation, I've already specified the upload path in the image model
upload_suffix = join('uploads/images', f.name)
upload_path = join(settings.MEDIA_ROOT, upload_suffix)
destination = open(upload_path, 'wb+')
for chunk in f.chunks():
destination.write(chunk)
destination.close()
return upload_suffix
def add_room(request, apartment_id, form_class=AddRoomForm, template='apartments/add_room.html'):
apartment = Apartment.objects.get(id=apartment_id)
if request.method == 'POST':
form = form_class(request.POST, request.FILES)
if form.is_valid():
room = form.save()
image_1 = form.cleaned_data['image_1']
# Instead of writing a special function to handle the image,
# shouldn't I just be able to pass it straight into Image.objects.create
# ...but it doesn't seem to work for some reason, wrong syntax perhaps?
upload_path = handle_uploaded_file(image_1)
image = Image.objects.create(content_object=room, image=upload_path)
return HttpResponseRedirect(room.get_absolute_url())
else:
form = form_class()
context = {'form': form, }
return direct_to_template(request, template, extra_context=context)
Why don't you just use ImageField? I don't see the need for the Image class.
# model
class Room(models.Model):
name = models.CharField(max_length=50)
image = models.ImageField(upload_to="uploads/images/")
# form
from django import forms
class UploadFileForm(forms.Form):
name = forms.CharField(max_length=50)
image = forms.FileField()
Take a look at Basic file uploads and How do I use image and file fields?
You don't have to use the Image class. As DZPM suggested, convert the image field to an ImageField. You also need to make some changes to the view.
Instead of using an upload handler, you can create a Image object with the uploaded data and attach the Image object to the Room object.
To save the Image object you need to do something like this in the view:
from django.core.files.base import ContentFile
if request.FILES.has_key('image_1'):
image_obj = Image()
image_obj.file.save(request.FILES['image_1'].name,\
ContentFile(request.FILES['image_1'].read()))
image_obj.save()
room_obj.image_set.create(image_obj)
room_obj.save()
Also, I think instead of the GenericRelation, you should use a ManyToManyField, in which case the syntax for adding an Image to a Room will change slightly.
What about using two forms on the page: one for the room and one for the image?
You'll just have to make the generic foreign key fields of the image form not required, and fill in their values in the view after saving the room.
Django does support your use case at least up to a point:
formsets display repeated forms
model formsets handle repeated model forms
inline formsets bind model formsets to related objects of an instance
generic inline formsets do the same for generic relations
Generic inline formsets were introduced in changeset [8279]. See the changes to unit tests to see how they are used.
With generic inline formsets you'll also be able to display multiple already saved images for existing rooms in your form.
Inline formsets seem to expect an existing parent instance in the instance= argument. The admin interface does let you fill in inlines before saving the parent instance, so there must be a way to achieve that. I've just never tried that myself.
I found this page look for a solution to this same problem.
Here is my info -- hopefully helps some.
MODELS: Image, Review, Manufacturer, Profile
I want Review, Manufacturer, Profile to have a relationship to the Image model. But you have to beable to have multiple Images per object. (Ie, One Review can have 5 images a different Review can have 3, etc)
Originally I did a
images = ManyToManyField(Image)
in each of the other models. This works fine, but sucks for admin (combo select box). This may be a solution for you though. I dont like it for what I'm trying to do.
The other thing I'm working on now is having multiple foreign keys.
class Image(models.Model):
description = models.TextField(blank=True)
image = models.ImageField(upload_to="media/")
user_profile = models.ForeignKey(UserProfile)
mfgr = models.ForeignKey(Manufacturer)
review = models.ForeignKey(Review)
but like you said. This is pretty sloppy looking and I just dont like it.
One other thing I just found but don't have my brain completely wrapped around (and not sure how transparent it is after implementation is Generic Relationships (or Generic Foreign Keys), which may be a solution. Well once I comprehend it all. Need more caffeine.
http://www.djangoproject.com/documentation/models/generic_relations/
Let me know if you get this sorted out or any of this helps. Thanks!
Let me know if this helps or you have a different solutions.
Ok I figured it out with some more reading... I feel like you want to do exactly what I have done so here it is.
I'll be using GenericForeignKeys for this.
First the imports for models.py
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
Now add the following to your Image Model
class Image(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
This lets this model be just that, a Generic Foreign Key for any number of models.
Then add the following to all the models you want to have related images
images = generic.GenericRelation(Image)
Now in admin.py you need to add the following things.
from django.contrib.contenttypes.generic import GenericTabularInline
class ImageInline(GenericTabularInline):
model = Image
extra = 3
ct_field_name = 'content_type'
id_field_name = 'object_id'
And then include it in a admin declaration
class ReviewAdmin(admin.ModelAdmin):
inlines = [ImageInline]
And thats it. Its working great over here. Hope this helps man!
.adam.
Use two forms, one for the room and one for the image:
class Image(models.Model)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
image = models.ImageField(upload_to='')
class UploadImage(forms.ModelForm):
class Meta:
model = Image
fields = ('image')
class Room(models.Model):
name = models.CharField(max_length=50)
images = models.ManyToManyField(Image)
class RoomForm(forms.ModelForm):
class Meta:
model = Room
in the views
if request.method == "POST":
##2 form, una per l'annuncio ed una per la fotografia
form = RoomForm(request.POST)
image_form = UploadImage(request.POST, request.FILES)
#my_logger.debug('form.is_valid() : ' + str(form.is_valid()))
if form.is_valid() and image_form.is_valid():
##save room
room = room.save()
##save image
image = image_form.save()
##ManyToMany
room.images = [image]
room.save()