Using Django 3 & Python3. What is the right way / best practice to implement this:
Say I have 2 models Project and Task like this:
class Project(models.Model):
name = models.CharField(max_length=120)
class Task(models.Model):
name = models.CharField(max_length=120)
And I need to be able to upload documents 'attached' to any of both models. I am doing:
class BaseDocument(models.Model):
name = models.CharField(max_length=80)
document_date = models.DateField()
def __str__(self):
return self.slug
class Meta:
abstract = True
class ProjectImage(BaseDocument):
image = models.ImageField(upload_to='images/', blank=True)
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='project_images', blank=True, null=True)
class ProjectDocument(BaseDocument):
raw_file = models.FileField(
upload_to='raw/', blank=True,
storage=RawMediaCloudinaryStorage()
)
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='project_documents', blank=True, null=True)
class TaskImage(BaseDocument):
image = models.ImageField(upload_to='images/', blank=True)
project = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='task_images', blank=True, null=True)
class TaskDocument(BaseDocument):
raw_file = models.FileField(
upload_to='raw/', blank=True,
storage=RawMediaCloudinaryStorage()
)
project = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='task_documents', blank=True, null=True)
And then creating urls, forms, CBVs for all the models.
This seems inefficient and not very DRY. The question basically is what is the right approach to implement a functionality like this so that the user is able to upload documents to different models.
I've been trying to implement a solution using contenttypes and GenericRelations (anticipating that I may need to attach images to other models in the future) with no success. I was not able to figure out how to implement an 'UploadView' (CreateView) that is able to create the Image/Document object and assign the content_object of the GenericRelation to project/task. I feel that this approach adds a level of complexity that I was not able to handle. If this was the right/best approach, could someone point me out to some tutorial/code sample that implements this?
I hope I am being clear enough. Thanks.
Related
I'm learning Django and I'm working in a test project.
So in my models.py I have this (I know that is wrong but I don't know how to do that):
from django.db import models
class Diagram(models.Model):
name = models.CharField(max_length=100)
img = models.ImageField(upload_to='img/diagrams/', help_text="Size required: height = 1050px, width = 1506px.", max_length=None)
def __str__(self):
return self.name
class DiagramReferences(Diagram):
quantity_of_references = models.IntegerField()
for i in range(quantity_of_references):
title = models.CharField(max_length=50)
coords = models.CharField(max_length=50)
So I want to make a relation between both models, the first: Diagram contains the name of the Diagram and the respective image. In the other I want to select the first which was already created and assign him how many references it has and create a field for each reference.
First you are going to create Diagram. you should create a function for your upload directory:
def user_directory_path_img(instance, filename):
# file will be uploaded to MEDIA_ROOT / user_<id>/<filename> if you've declared MEDIA path
return 'img/diagrams/{0}/{1}-{2}'.format(instance.name, datetime.now(tz=pytz.UTC).date(), filename)
class Diagram(models.Model):
name = models.CharField(max_length=100)
img = models.ImageField((upload_to=user_directory_path_img, null=True, blank=True)
def __str__(self):
return self.name
Then you are going to make a ForeignKey field . As vinkomlacic said you can not create dynamic relations and its advised not to mess with that. If you want to have different fields either make a TextField with multiple entries or arrays or just create the maximum number of fields that you think are required and set them to blank=True, null=True.
class DiagramReferences(models.Model):
diagram = models.ForeignKey(Diagram, on_delete=models.CASCADE, blank=True, null=True, default=None)
title = models.CharField(max_length=50, blank=True, null=True)
coords = models.CharField(max_length=50, blank=True, null=True)
extra = models.CharField(max_length=50, blank=True, null=True)
I'm trying to make a Description model that can be used by several different models.
But the Description model can only have one at a time.
Lets say that i have these two models.
class Account(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
birthdate = models.DateField()
phoneNumber = models.CharField(max_length=20)
class CompanyProfile(models.Model):
companyName = models.CharField(max_length=50)
VAT = models.CharField(max_length=35, unique=True)
I want both to have multiple descriptions. But the description object can only be linked to one model. A (dirty) solution that i found was this.
class Description(models.Model):
title = models.CharField(max_length=100)
text = models.CharField(max_length=2500, null=True, blank=True)
account = models.ForeignKey(Account, on_delete=models.CASCADE, null=True, blank=True)
company = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True, blank=True)
In normal python you would just be able to have a list of description. But in django, this isn't possible. I've thought about using generic relations but that seems to work differently.
Why I want to do it like this?
Because I want to expand Description by using inheritance and I want to be able to use all those types of 'Descriptions' for multiple classes.
Django has got Generic ForeignKey
But I feel a better way to implement this is to add OneToOne field in each model mapping to Description model
description= models.ForeignKey(Description, on_delete=models.SET_NULL, null=True, blank=True)
E.g. there are next models and custom QuerySet:
from django.db import models
class ActiveQuerySet(models.QuerySet):
def active(self):
'''returns only active objects'''
'''supposing here would be a lot
of more complicated code
that would be great to reuse
'''
return self.filter(is_active=True)
class Category(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, default=None)
class Product(models.Model):
name = models.CharField(max_length=128, blank=True, null=True)
is_active = models.BooleanField(default=True)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, related_name='products', related_query_name="product", blank=True, null=True, default=None)
objects = ActiveQuerySet.as_manager()
Could you tell me if there is a way to call the active() method like this:
category = Category.objects.first()
category.products.active()
instead of doing like this:
category.products.filter(is_active=True)
Or how to implement appropriately such behavior?
You have to add the custom queryset into the parent model, not the _set. You can change custom_products to any other word.
class ActiveQuerySet(models.QuerySet):
def active(self):
'''returns only active objects'''
'''supposing here would be a lot
of more complicated code
that would be great to reuse
'''
return self.products.filter(is_active=True)
class Category(models.Model):
name = models.CharField(max_length=128, blank=True, null=True, default=None)
custom_products = ActiveQuerySet.as_manager()
category.custom_products.active()
A little bit of info. I want the user to be able to view their feed which contains friends' posts, group posts, etc. I am using Django Rest Framework and providing this feed endpoint to a frontend.
My initial thought was just making separate models(tables) per items I needed. Like a UserPost, GroupPost, EventPost, but I feel when trying to consolidate the data it will just be doing a lot of joins and having to stitch together the data.
ex)
class UserPost(models.Model): # GroupPost, EventPost
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
# group/event = models.ForeignKey(Group/Event, on_delete=models.CASCADE)
This didn't seem like a good approach in case we wanted to add post type functionality for other models.
My other approach is using intermediate models. Post model is the base and UserPost, GroupPost, EventPost being the intermediate models having a OneToOne relationship to post and GroupPost would have a foreign key (OneToMany) relationship to a Group model.
ex)
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
class UserPost(UUID_PK, models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_uPost', on_delete=models.CASCADE)
class Group(models.Model):
name = models.CharField(max_length=64)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_groups')
members = models.ManyToManyField(settings.AUTH_USER_MODEL)
class GroupPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_gPost', on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
class Event(models.Model):
name = models.CharField(max_length=64)
about = models.TextField(null=True, blank=True)
event_date = models.DateTimeField(null=True, blank=True)
invited = models.ManyToManyField(settings.AUTH_USER_MODEL)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_events')
class EventPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_ePost', on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
This approach isn't too bad, but it does require an extra query now.
Then to mimic getting a user's "feed" I would filter like this
users = # friends
groups = # groups of the user
events = # events of the user
posts = Post.objects.filter(Q(created_by__in=users, _gPost__isnull=True, _ePost__isnull=True) | Q(
_gPost__group__in=groups) | Q(_ePost__event__in=events)).distinct().select_related('created_by')
# _gPost__isnull=True and _ePost__isnull=True is exclude the other types of post and only get the "normal" posts.
which just looks awful.
I am unsure if this is a good enough approach or if anyone else has recommended for improving this.
I did look into GenericRelationship and wasn't sure if that would actually make this better. I have a few models that deal with GenericRelationship and for the most part, have been a bit of a pain.
I am trying to learn django by creating a blog on my own, and I've tried some real simple steps before, but now I want to make something slightly more complex. Currently, I am thinking of dividing the blogs' 'Stories' into 'Blocks'. My idea is to have two child classes 'TextBlock' and 'ImageBlock', and my current models look like this.
class Story(models.Model):
writer = models.CharField(max_length=189)
title = models.CharField(max_length=189)
class Block(models.Model):
story = models.ForeignKey(Story, related_name='+', on_delete=models.CASCADE)
block = EnumField(choices=[
('TXT', "text"),
('IMG', "image"),
])
serial = models.IntegerField(default=0)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
class TextBlock(Block):
type = models.CharField(max_length=128, blank=True, null=True, default='paragraph')
content = models.TextField(blank=True, null=True)
class ImageBlock(Block):
src = models.URLField()
type = models.CharField(max_length=128, blank=True, null=True, default='full')
title = models.CharField(max_length=189, blank=True, null=True)
photographer = models.CharField(max_length=128, blank=True, null=True)
What I'd like to do now is to create blog entries from the django admin interface. Is it possible to create both types from the main Block? Or do I need to go to both TextBlock and ImageBlock? Any ideas on how I should proceed from here? Thanks.