I am just starting Django Website;
I have deciced to go with a model (with SQLite DB), which have the following properties:
class Flow(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="Owner", default="ADMIN")
source = models.CharField(default='HTTP', choices=SOURCE_CHOICES, editable=True, max_length=12)
name = models.CharField(max_length=50, default=" ")
date = models.DateTimeField(verbose_name="Creation date")
I want to add others fields to this model depending on the source field value.
For example if the source field : 'File' is selected. I will create additionnal field (like file name, file directory ...) If 'Http' is selected, I will create a field URL.
Thus depending on the source field, I will have differents field and type.
I have read this kind of model is difficult to reprensent in Django; I am open minded to other kind of solution. My idea was to created as many model as the possible source value.
Even if you can create dynamic field in Django, you can't create dynamic column in Sqlite table FLOW.
If you plan to use same types of fields in different cases, you can create field with abstract name, for example path. That can be as URL as local file path.
In common way you need to create all columns for all choices for DB table.
class Flow(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="Owner", default="ADMIN")
source = models.CharField(default='HTTP', choices=SOURCE_CHOICES, editable=True, max_length=12)
name = models.CharField(max_length=50, default=" ")
date = models.DateTimeField(verbose_name="Creation date")
local_path = models.ImageField(upload_to='files', null=True, blank=True) # when source is file
url = models.URLField(null=True, blank=True) # when source is http
So local_path may be empty when you using HTTP source.
Inside view you can dynamically get (or set) value through serializer:
class FlowSerializer(serializers.ModelSerializer):
path = serializers.SerializerMethodField(method_name='get_path_value')
class Meta:
model = Flow
fields = ('owner', 'source', 'name', 'date', 'path')
def get_path_value(self, instance):
if instance.source == 'HTTP':
return instance.url
else:
return instance.local_path
So path will be different for different sources.
And maybe you will be need to install django rest framework for this solution.
EDIT1:
answering to question
So if I understand well, the best pratices should be to create 'blank'
columns
You definitely must to describe all columns in table (unless you using non Sql-like DB, such as MongoDB). So yes, create 'blank' columns, is only one possible way.
But you can override save method in model, for dynamically save fields:
class Flow(models.Model):
temp_path = None
path = models...
url = models...
choice = models...
def save(self, *args, **kwargs):
if choice == 'HTTP':
self.url = temp_path
else:
self.path = temp_path
super().save(*args, **kwargs)
Code above is just a quick idea. Not really working code.
You could implement a custom model field type for that, it helps you save a union object in one column in database. The raw type in database could be a JSON string or other serialized types, for the model field's usage, it's just a python native object!
Here is a piece of sample code: the main jobs are these two methods:
class HandField(models.Field):
# ...
def from_db_value(self, value, expression, connection):
if value is None:
return value
return parse_hand(value)
def to_python(self, value):
if isinstance(value, Hand):
return value
if value is None:
return value
return parse_hand(value)
Check the official docs for details.
Related
Context: I'm forcing my self to learn django, I already wrote a small php based website, so I'm basically porting over the pages and functions to learn how django works.
I have 2 models
from django.db import models
class Site(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Combo(models.Model):
username = models.CharField(max_length=50)
password = models.CharField(max_length=50)
dead = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True)
siteID = models.ForeignKey(Site, on_delete=models.PROTECT)
class Meta:
unique_together = ('username','siteID')
def __str__(self):
return f"{self.username}:{self.password}#{self.siteID.name}"
When creating a view, I want to get the Combo objects, but I want to sort them first by site name, then username.
I tried to create the view, but get errors about what fields I can order by Cannot resolve keyword 'Site' into field. Choices are: dead, id, password, siteID, siteID_id, timestamp, username
def current(request):
current = Combo.objects.filter(dead=False).order_by('Site__name','username')
return render(request, 'passwords/current.html',{'current':current})
Since I'm not necissarily entering the sites into the database in alphabetical order, ordering by siteID wouldn't be useful. Looking for some help to figure out how to return back the list of Combo objects ordered by the Site name object then the username.
You can order this by siteID__name:
def current(request):
current = Combo.objects.filter(dead=False).order_by('siteID__name','username')
return render(request, 'passwords/current.html',{'current':current})
since that is the name of the ForeignKey. But that being said, normally ForeignKeys are not given names that end with an ID, since Django already adds an _id suffix at the end for the database field.
Normally one uses:
class Combo(models.Model):
# …
site = models.ForeignKey(Site, on_delete=models.PROTECT)
if you want to give the database column a different name, you can specify that with the db_column=… parameter [Django-doc]:
class Combo(models.Model):
# …
site = models.ForeignKey(
Site,
on_delete=models.PROTECT,
db_column='siteID'
)
Looking at graphene_django, I see they have a bunch of resolvers picking up django model fields mapping them to graphene types.
I have a subclass of JSONField I'd also like to be picked up.
:
# models
class Recipe(models.Model):
name = models.CharField(max_length=100)
instructions = models.TextField()
ingredients = models.ManyToManyField(
Ingredient, related_name='recipes'
)
custom_field = JSONFieldSubclass(....)
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = ???
I know I could write a separate field and resolver pair for a Query, but I'd prefer it to be available as part of the schema for that model.
What I realize I could do:
class RecipeQuery:
custom_field = graphene.JSONString(id=graphene.ID(required=True))
def resolve_custom_field(self, info, **kwargs):
id = kwargs.get('id')
instance = get_item_by_id(id)
return instance.custom_field.to_json()
But -- this means a separate round trip, to get the id then get the custom_field for that item, right?
Is there a way I could have it seen as part of the RecipeType schema?
Ok, I can get it working by using:
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = graphene.JSONString(resolver=lambda my_obj, resolve_obj: my_obj.custom_field.to_json())
(the custom_field has a to_json method)
I figured it out without deeply figuring out what is happening in this map between graphene types and the django model field types.
It's based on this:
https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers
Same function name, but parameterized differently.
I'm uploading a file and additionally some data like file's id and file's title to the server. I have the view below to handle the request and I want to save the file to a dynamic path like upload/user_id/thefile.txt.
With code the below the file will be saved in and upload folder directly and also my product_video table will create a new record with the related id and the title.
Now I don't have any idea how can I save the file in a dynamically generated directory like: upload/user_id/thefile.txt and how to save the produced path to the database table column video_path?
view class:
class FileView(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request, *args, **kwargs):
if request.method == 'POST' and request.FILES['file']:
myfile = request.FILES['file']
serilizer = VideoSerializer(data=request.data)
if serilizer.is_valid():
serilizer.save()
fs = FileSystemStorage()
fs.save(myfile.name, myfile)
return Response("ok")
return Response("bad")
and serializer clas:
class VideoSerializer(ModelSerializer):
class Meta:
model = Product_Video
fields = [
'p_id',
'title',
'video_length',
'is_free',
]
and related model class:
def user_directory_path(instance, filename):
return 'user_{0}/{1}'.format(instance.user.id, filename)
class Product_Video(models.Model):
p_id = models.ForeignKey(Product, on_delete=models.CASCADE, to_field='product_id', related_name='product_video')
title = models.CharField(max_length=120, null=True,blank=True)
video_path = models.FileField(null=True, upload_to=user_directory_path,storage=FileSystemStorage)
video_length = models.CharField(max_length=20, null=True, blank=True)
is_free = models.BooleanField(default=False)
You put the cart before the horse. Start from the beginning, first things first. And the first thing is the user story, then the model layer.
There are Products and a Product can have many ProductVideos. A Product has an Author (the Author can have many Products). When you upload a ProductVideo for a particular Product, you want to save it in a directory which contains the Author ID.
Therefore we specify a callable for the FileField which should dynamically find out the id of the Author:
def user_directory_path(instance, filename):
# get the id of the author
# first get the Product, then get its author's id
user_id = str(instance.p_id.author.id)
# little bit cosmetics
# should the filename be in uppercase
filename = filename.lower()
return 'user_{0}/{1}'.format(user_id, filename)
When saving an instance of Product_Video the uploaded file should be stored in a directory with a dynamically created pathname based on the author's ID.
Further I'd suggest you to follow established coding conventions:
don't use snake_case for class names, especially don't mix it with capital letters. Use PascalCase, preferably singular nouns, for class names: ProductVideo
if your model class is ProductVideo, keep that name in all subsequent classes: ProductVideoSerializer for the model serializer and ProductVideoAPIView for the generic view.
use verbs for methods and standalone functions, get_user_directory or upload_to_custom_path is better than user_directory_path.
also avoid suffixing the foreign key fields with _id. Use rather product instead of p_id. Django will allow you to access the related object by calling product and get the id of the object by calling product_id. Using the name p_id means you'll access the id of the related object by calling p_id_id which looks very weird and confusing.
I'm stuck using a non-Django legacy MySQL database. I need to write code that generates a unique filename each time a model object is saved. The following doesn't work. It won't overwrite the filename field. It saves fine with whatever the filename field was set to. Is this because that field is set as the primary key?
(I realize my code isn't creating a random filename--haven't gotten that far yet. Also, I know this will only save once since it needs to be unique, but it won't even save the first time).
class Agenda(models.Model):
type = models.IntegerField()
filename = models.CharField(max_length=45, primary_key=True)
date = models.DateField()
class Meta:
managed = False
db_table = 'gbminutes'
def save(self, *args, **kwargs):
self.filename = 'ATESTFILE'
super(Agenda, self).save(*args, **kwargs)
I have a Django model that looks like this:
class Categories(models.Model):
"""
Model for storing the categories
"""
name = models.CharField(max_length=8)
keywords = models.TextField()
spamwords = models.TextField()
translations = models.TextField()
def save(self, force_insert=False, force_update=False):
"""
Custom save method that converts the name to uppercase
"""
self.name = self.name.upper()
super(Categories, self).save(force_insert, force_update)
Whenever the data is inserted or updated. I'd like to check that that a record with same name doesn't exists. It's a unique constraint that I'd like to implement via code and not the DB. The amount of data in this table is minuscule so the the performance hit is not an issue. If there is an constraint violation, I'd like to raise one of Django's inbuilt constraint exceptions instead of creating a custom one.
Could someone how me the best/fastest way to accomplish this?
Thanks.
In your model definition you can tell Django that 'name' should be unique:
name = models.CharField(max_length=8, unique=True)
A django.db.IntegrityError will be raised if you attempt to save two records with the same name.
in the view
try:
Category.objects.get(name='name')
except Category.DoesNotExist:
# call the save method of model