Django - Accessing attributes through a OneToOneField - python

I am very new to Django and I was wondering if I could request some help with an issue I am facing. I'm trying to build a set of models in Django that is structured as follows:
An app_user describes a user of the application I am building.
An app_job describes a job that the user wants to run on the app, with several associated inputs (upload1, upload2, upload3). A user can run many jobs; hence, I use the many-to-one (ForeignKey) relationship between job and user. When an app_job is created, I want its associated files to be uploaded to a directory determined by the associated user's username and num_jobs attribute, as shown.
When I run python manage.py makemigrations, I receive the following error: AttributeError: 'ForeignKey' object has no attribute 'user'. Which begs the question, how can I access the underlying app_user's information from the app_job class?
Thanks for the help.
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
class app_user(models.Model):
user = models.OneToOneField(User)
num_jobs = models.IntegerField(default = 0)
def __unicode__(self):
return self.user.get_username()
class app_job(models.Model):
app_user = models.ForeignKey(app_user)
upload1 = models.FileField(upload_to = app_user.user.get_username() + "/" + str(app_user.num_jobs) , blank = True, null = True)
upload2 = models.FileField(upload_to = app_user.user.get_username() + "/" + str(app_user.num_jobs))
upload3 = models.FileField(upload_to = app_user.user.get_username() + "/" + str(app_user.num_jobs) )

Okay there are a couple of issues here. But nothing a little education cannot fix.
models should be CamelCased this makes it easier to read and is generally good practise.
You do not need to prefix models with app_ its much cleaner and easier to read without this.
Anyway,
You app_job model ForeignKey should be to User not to the app_user model. By doing this you can still gain access to the app_user data.
You also need to modify the upload_to attributes also. Whilst uploads_to can be a string value it cannot be evaluated the way you are currently doing this. Check out the django filefield documentation for details (shameless plug, I recently re-wrote this part of the documentation).
Instead you need to do the following:
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class app_job(models.Model):
app_user = models.ForeignKey(User)
upload1 = models.FileField(upload_to=user_directory_path , blank = True, null = True)
upload2 = models.FileField(upload_to=user_directory_path
upload3 = models.FileField(upload_to=user_directory_path, blank=True, null=True)
What this is doing is upload_to calls the function user_directory_path to generate the file path.
By following the above you should have something like:
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
class UserProfile(models.Model):
"""
In settings.py you will want to add a link to AUTH_USER_PROFILE
See https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-the-existing-user-model
"""
user = models.OneToOneField(User)
num_jobs = models.IntegerField(default=0)
def __unicode__(self):
return self.user.get_username()
def upload_dir(instance, filename):
return instance.user.get_username() + "/" + str(instance.user.num_jobs)
class Job(models.Model):
user = models.ForeignKey(User)
upload1 = models.FileField(upload_to=upload_dir, blank = True, null = True)
upload2 = models.FileField(upload_to=upload_dir, blank=True, null=True)
upload3 = models.FileField(upload_to=upload_dir, blank=True, null=True)

Based on this answer to a similar question something like this should work:
# models.py
from django.db import models
from django.contrib.auth.models import User
class app_user(models.Model):
user = models.OneToOneField(User)
num_jobs = models.IntegerField(default = 0)
def __unicode__(self):
return self.user.get_username()
def content_file_name(instance, filename):
return '/'.join([instance.app_user.user.get_username(), str(instance.app_user.num_jobs), filename])
class app_job(models.Model):
app_user = models.ForeignKey(app_user)
upload1 = models.FileField(upload_to = content_file_name , blank = True, null = True)
upload2 = models.FileField(upload_to = content_file_name)
upload3 = models.FileField(upload_to = content_file_name)

As you have been told there are several issues. But its good to learn :).
First the class name should start with an upper letter and use "camelCase",
in your case, "AppUser" and "AppJob". Personally I would not use "App" as suffix instead I would just use "MyUser" and "Job".
The error you are getting "'ForeignKey' object has no attribute 'user" is because in your "app_job" model you have a ForeignKey to app_user, which is OK, that creates the relationship between the both models, but then in the FileField's you are using that "foreignkey" object which have the same name as your model "app_user" and that ForeignKey instance does not have an attribute called "user", Do you get this?
The information the guys gave you related to the "upload_to" is correct :).
you can get more info in the Django docs
Thanks

Related

Django per-model authorization permissions

Im facing a problem in Django with authorization permissions (a bit new to Django).
I have a teacher, student and manager models.
When a teacher sends a request to my API they should get different permissions than a student (ie, a student will see all of his own test grades, while a teacher can see all of its own class's students, and a manager can see everything).
My questions are as follows:
How do I make all of my models valid system users? I've tried adding
models.OneToOneField(User, on_delete=models.CASCADE)
But this requires creating a user, and then assigning it to the teacher. What I want is for the actual teacher "instance" to be the used user.
How do I check which "type" is my user ? if they are a teacher, student or manager? do I need to go over all 3 tables every time a user sends a request, and figure out which they belong to ? doesnt sound right.
I thought about creating a global 'user' table with a "type" column, but then I wont be able to add specific columns to my models (ie a student should have an avg grade while a teacher shouldn't) .
Would appreciate any pointers in the right direction.
When you need multiple user types, for example, in your case multiple roles are needed like a student, teacher, manager, etc… then you need a different role for all the persons to categorize.
To have these roles you need to extend AbstractUser(for simple case) in your models.py for your User model also You can specify permissions in your models. Attaching permissions is done on the model's class Meta using the permissions field. You will be able to specify as many permissions as you need, but it must be in a tuple like below:
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models.fields.related import ForeignKey
from django.utils.translation import gettext as _
class Role(models.Model):
STUDENT = 1
TEACHER = 2
MANAGER = 3
ROLE_CHOICES = (
(STUDENT, 'student'),
(TEACHER, 'teacher'),
(MANAGER, 'manager'),
)
id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)
def __str__(self):
return self.get_id_display()
class User(AbstractUser):
roles = models.ManyToManyField(Role)
username = models.CharField(max_length = 50, blank = True, null = True, unique = True)
email = models.EmailField(_('email address'), unique = True)
native_name = models.CharField(max_length = 5)
phone_no = models.CharField(max_length = 10)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
def __str__(self):
return "{}".format(self.email)
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='students')
sample_field_name = models.CharField(max_length = 50, blank = True, null = True)
class Meta:
permissions = (("sample_permission", "can change sth of sth"),)
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='teachers')
sample_field_name = models.CharField(max_length = 50, blank = True, null = True)
class Meta:
permissions = (("sample_permission", "can change sth in sth"),)
class Manager(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='managers')
sample_field_name = models.CharField(max_length = 50, blank = True, null = True)
class Meta:
permissions = (("sample_permission", "can change sth in sth"),)
After that you should have your permissions for your views and Adding permissions to restrict a function to only users that have that particular permission can be done by using a Django built-in decorator, permission_required for function-based views::
from django.contrib.auth.decorators import permission_required
#permission_required('students.sample_permission')
def student_sample_view(request):
"""Raise permission denied exception or redirect user"""
And if you are using a class-based view, you just need to use a mixin, PermissionRequiredMixin:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView
class SampleListView(PermissionRequiredMixin, ListView):
permission_required = 'students.sample_permission'
# Or multiple permissions
permission_required = ('students.sample_permission', 'teachers.other_sample_permission')
This was one way you can manage multiple roles in your Django project,
you can also find more ways in below blogs and references:
How to Implement Multiple User Types with Django
Managing User Permissions in Django
Supporting Multiple Roles Using Django’s User Model
Django Roles, Groups and Permissions Introduction
django-multiple-user-types-example GitHub repository

How to generate a choices tuple for django models field to use in a form

Trying to create a choice field based on another model
I want my choices to be mapped like username: first_name + last_name
When I try username: last_name it does work
I tried doing something like this(Note, I am adding on user_choices and choices=user_choices. The model already existed before me making these changes.)
This works:
he_user_choices = tuple(User.objects.values_list('username', 'last_name'))
Here's what my models.py looks like:
from django.contrib.auth.models import User
owner_choices = tuple(User.objects.values_list('username', 'first_name' + 'last_name'))
class ErrorEvent(models.Model):
"""Error Event Submissions"""
event_id = models.BigAutoField(primary_key=True)
owner = models.IntegerField(verbose_name="Owner", blank=True, choices=owner_choices)
and here's my forms.py
from django import forms
from .models import ErrorEvent
class ErrorEventForm(forms.ModelForm):
class Meta:
model = ErrorEvent
# fields =
exclude = ['event_id']
widgets = {
'owner': forms.Select(),
}
Currently the owner_choices doesn't work, I get an error that says:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Any recommendations on what else I can try, or how would I go about fixing my problem?
Thank you in advance!
Please do not work with an IntegerField to refer to an object. A ForeignKey will check referential integrity, and furthermore it makes the Django ORM more expressive.
You thus can implement this with:
from django.conf import settings
class ErrorEvent(models.Model):
event_id = models.BigAutoField(primary_key=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name='Owner',
blank=True,
null=True
)
This will work with a ModelChoiceField [Django-doc] that automatically will query the database to render options. This also means that if you add a new User, creating a new ErrorEvent can be linked to that user, since it each time requests the Users from the database.
You can subclass the ModelChoiceField to specify how to display the options, for example:
from django.forms import ModelChoiceField
class MyUserModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return f'{obj.username} ({obj.firstname} {obj.lastname})'
Then we can use this in the form:
class ErrorEventForm(forms.ModelForm):
owner = MyUserModelChoiceField(queryset=User.objects.all())
class Meta:
model = ErrorEvent
# fields =
exclude = ['event_id']
widgets = {
'owner': forms.Select(),
}

Unique filename of uploaded file using the django FORM

I'm trying to generate a unique filename for the uploaded file using the Django forms. I've tried uuid_upload_path app but that app doesn't work with the form. Below is my code
Forms.py
class HelpGuideForm(forms.ModelForm):
title = forms.CharField(max_length = 50)
image = forms.ImageField(required = False)
class Meta:
model = Helpguide
fields = ['title', 'image']
Models.py
from uuid_upload_path import upload_to
class HelpguideImage(models.Model):
image = models.ImageField(upload_to = upload_to, blank=True, null=True)
I want a unique name for all uploaded files. something like sd564sadasd61.jpg. I'm using Django 2.2
In your Model you can set the upload_to of the imagefield to a function and then generate the uuid.
A very simple (untested) example:
import uuid
Class MyModel(models.Model):
def get_path(instance, filename):
extension = filename.split('.')[-1]
uuid = uuid.uuid1().hex
return f'path/to/file/{uuid}.{extension}'
image = ImageField(upload_to=get_path)
What I understand of your problem, you can set initial for FORM class when initialising it. like:
help_guide_form = HelpGuideForm(initial={'headline': uuid.uuid4().hex}, instance= Helpguide)
from django docs. Also see the initial reference.

How to set a variable from one class equals to a variable in another class in Django models.py?

I am a new in Django world and I want to link two classes from models.py so that i can set their variables equal to each other. Here is the models.py code:
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class file(models.Model):
title = models.CharField(max_length=250)
FILE_TYPE_CHOICES = (
('audio','Audio'),
('games','Games'),
('videos','Videos'),
('applications','Applications'),
('books','Books/Docs'),
('others','Others')
)
file_type = models.CharField(max_length=10,choices=FILE_TYPE_CHOICES,default='others')
description = models.TextField(max_length=6000)
#uploader_username = ???
def get_absolute_url(self):
return reverse('one:user')
def __str__(self):
return self.title
class user (models.Model):
username= models.CharField(max_length=100)
email=models.EmailField
password= models.CharField(max_length = 100)
user_files = models.ForeignKey(file, on_delete=models.CASCADE)
Here I want to set uploader_username from file class equals tousername from user class.
No, you don't want to do this. You want a ForeignKey from File to User, not the other way round, then you can just access my_file.user.username.
Note, it is a bad idea to define your own user class like this. There can be good reasons for doing so, but if so you must inherit from the abstract base classes in the auth app; failure to do so is a serious security problem as you will be storing passwords in clear text. It doesn't look like you need your own model here; you should remove this class.

Django no such column ForeignKey

I'm doing a Django project (kind of social network) and want to have a page where I can see all my posts, which I did.
I allways get the error: no such column: uploaded_by
in models.py
from django.db import models
from django.contrib.auth.models import User
class ContentItem(models.Model):
upload_date = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=100, default='no title')
description = models.CharField(max_length=400, default='no description')
image = models.ImageField(upload_to='image_board/posts/', default='null')
uploaded_by = models.ForeignKey(User, default='0')
def __str__(self):
return self.title
in views.py
def view_my_favorites(request):
all_posts = ContentItem.objects.raw('SELECT * FROM image_board_ContentItem WHERE uploaded_by = request.user.username')
template = loader.get_template('favorites.html')
context = {
'all_posts': all_posts,
}
return HttpResponse(template.render(context, request))
I want to get the user name of the user who is loged in, how can i whrite this in the sql query?
Thaks guys :)
Your actual issue is probably caused by neglecting to make and run migrations after adding the uploaded_by field.
But there are a huge number of other things wrong here.
Firstly, you are comparing the uploaded_by column with a non-existent column, request.user.username. You need to use the actual value of that variable.
Secondly, you are comparing a foreign key - uploaded_by - with a string, username. These will never match.
Thirdly, you are using a raw query. There is absolutely no need to do that here.
Your query is trivial to express in the Django query syntax. You should do:
all_posts = ContentItem.filter(uploaded_by=request.user)
or even simpler:
all_posts = request.user.contentitem_set.all()

Categories

Resources