Django get and use instance and field name on upload_to - python

I'm trying to build a path for a FileField, getting and using the instance to get another data for the URL, plus the field name, to get something like:
/media/documents/<instance_data>/<field_name>.pdf
My best working approach is:
class UserDocFileField(models.FileField):
def get_fixed_folder_path(self, instance, filename):
return 'documents/{}/{}.pdf'.format(instance.user.rfc, self.name)
def __init__(self, *args, **kwargs):
kwargs["upload_to"] = self.get_fixed_folder_path
super(UserDocFileField, self).__init__(*args, **kwargs)
And in my model:
class Documents(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
file_1 = UserDocFileField()
file_2 = UserDocFileField()
# ... other documents
Giving me what I'm looking for, i.e.:
/media/documents/ABCD840422ABC/file_1.pdf
However, this makes Django to generate a migration file every single time I run makemigrations, I have tried to set it as an inner class, rewriting the super as
super(Documents.UserDocFileField, self).__init__(*args, **kwargs)
But, I got this error:
NameError: name 'Documents' is not defined
So, is there a way to avoid the generations of migrations files or a better approach to solve this?

One way of doing this is to use a custom class for the upload_to itself, with a __call__ method to make the instance callable. In order to make that serializable for migrations you then need to add a deconstruct method. So:
class UploadTo:
def __init__(self, name):
self.name = name
def __call__(self, instance, filename):
return 'documents/{}/{}.pdf'.format(instance.user.rfc, self.name)
def deconstruct(self):
return ('myapp.models.UploadTo', [self.fieldname], {})
class Documents(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
file_1 = FileField(upload_to=UploadTo('file_1'))
file_2 = FileField(upload_to=UploadTo('file_2'))
Honestly though, at this point I'd probably just write separate upload_to functions for each field.

Related

Django: modify model’s field before save

I have a model, course, with an ImageField and FileField so I’d like to create a folder each time the user create a course. I think I can do this before the model is saved so here is my question.
How can I access a model’s fields in a method?
Models.py
Class Course(models.Model):
Thumbnail = model.ImageField(upload_to=“...”,...)
def save(self, *args, **kwargd):
... #How can I alter here the ImageField parameters?
super().save(*args, **kwargs)
See here on getting the model's fields.
To get field of an object instance then it should just be as
def save(self, *args, **kwargs):
#### How can I alter here the ImageField parameters?
self.Thumbnail = #do whatever you want here
super().save(*args, **kwargs)
It's not possible to alter field's params in a method. It must only be done in the field definitions because the model must be saved first
Instance of the models field can be accessed using self like a normal instance variable.
For example in a model class below,
class DummyModel(models.Model):
key = models.CharField()
value = models.TextField()
def save(self, *args, **kwargs):
value = self.value # self.value is a model field.
super().save(*args, **kwargs)
In your case, you can access it using self.Thumbnail
Since it's not possible to modify the model field's parameters before create it, as #azyCrw4282 said in his answer, it's possible to create a directory with the name of the model's instance and upload there the files parsing a function to upload_to
models.py
def upload_to(instance, filename):
return 'user_course/{0}/{1}'.format(instance.name, filename) #instance.name will be the name of the course
class Course(models.Model):
name = model.CharField(...)
thumbnail = models.ImageField(upload_to=upload_to, ...)

Self-referencing class variable in Python

I'm trying to implement a folder-like type in Graphene-Django. A folder can contain files or folders. Doing this:
Django model:
from django.db import models
class Folder(models.Model):
name = models.CharField(...)
parent = models.ForeignKey('self')
class File(models.Model):
name = models.CharField(...)
content = models.TextField(...)
Graphene API:
from files.models import Folder, File
class FolderNode(DjangoObjectType):
folders = graphene.List(FolderNode)
def resolve_folders(self, args, context, info):
return Folder.objects.filter(parent=self)
class Meta:
model = Folder
fails, because I can't refer to FolderNode in its own class definition. Applying the answer to another question:
class FolderNode(DjangoObjectType):
def __new__(cls, *args, **kwargs):
cls.folders = graphene.List(cls)
return object.__new__(cls, *args, **kwargs)
def resolve_folders(self, args, context, info):
return Folder.objects.filter(parent=self)
class Meta:
model = Folder
doesn't work either, because Graphene only sees class variables that are part of the declaration in deciding what to add to the API. Any ideas?
Based on https://github.com/graphql-python/graphene/issues/110, the correct way to do this is with a string:
class FolderNode(DjangoObjectType):
folders = graphene.List('FolderNode')
An idea: what about defining a property not considered at class creation, as follows
class FolderNode(DjangoObjectType):
#property
def folders(self):
return graphene.List(FolderNode)

Django custom user model with django-polymorphic

I have some problems with getting correct model names when try to get it via user = request.user, it shows every user like User, even if it is Student.
Okay, more details here:
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
class UserManager(BaseUserManager):
pass
class User(AbstractBaseUser):
objects = UserManager()
pass
class Student(User):
pass
When in view code I try to get user = request.user, I need to get user model name to show them different views. I tried it with django-polymorphic, it works good for other objects but when I try to inherit from it everything goes down, I do not understand how to do it.
i tried something like this:
class User(AbstractBaseUser, PolymorphicModel):
pass
and in manager:
class UserManager(BaseUserManager, PolymorphicManager):
pass
but it doesnt work, could someone please show me how to configure them together or say better way to get correct model names from request.user
I found some decicion, here is code:
class InheritanceMetaclass(ModelBase):
def __call__(cls, *args, **kwargs):
obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
return obj.get_object()
class User(AbstractBaseUser):
__metaclass__ = InheritanceMetaclass
object_class = models.CharField(max_length=20)
objects = BaseUserManager()
def get_object(self):
if not self.object_class or self._meta.model_name == self.object_class:
return self
else:
return getattr(self, self.object_class)
def save(self, *args, **kwargs):
if not self.object_class:
self.object_class = self._meta.model_name
super(User, self).save( *args, **kwargs)
# inherits
class User1(User):
text1 = models.CharField(max_length=20)
text2 = models.CharField(max_length=20)
class User2(User):
text3 = models.CharField(max_length=20)
text4 = models.CharField(max_length=20)
It seams it works but I am sure should be better way to do it.
now you can get user class name in object_class attribute, user.object_class
1 more way here:
def get_class_name(instance):
return instance.__class__.__name__
and then:
classname = get_class_name(user)
But I do not think it is good ways anyways...

django model instance variables for transient use

I would like to create some instance variables for my model subclass, but when saving the object to the database I don't want a table column for that variable. I read in some places you would do this by overriding init() like how you would create normal instance variables in other classes. Is this the accepted way for subclasses of model? Are there other approaches?
models.py:
class MyModel (models.Model):
name = models.CharField(max_length=300)
def __init__(self, *args, **kwargs):
super(MyModel, self).__init__(*args, **kwargs)
self.tempvar = ''
views.py:
myModel = MyModel()
myModel.tempvar = 'this will not be saved in the database'
That's an acceptable way of doing it, although you don't need to initialize it unless there's a chance you may try to access it when it doesn't exist. Also, consider if you should be using a property instead.
Update for Python 3: You can call super() directly without repeating the class name
class MyModel (models.Model):
name = models.CharField(max_length=300)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tempvar = ''

Changing case (upper/lower) on adding data through Django admin site

I'm configuring the admin site of my new project, and I have a little doubt on how should I do for, on hitting 'Save' when adding data through the admin site, everything is converted to upper case...
Edit: Ok I know the .upper property, and I I did a view, I would know how to do it, but I'm wondering if there is any property available for the field configuration on the admin site :P
If your goal is to only have things converted to upper case when saving in the admin section, you'll want to create a form with custom validation to make the case change:
class MyArticleAdminForm(forms.ModelForm):
class Meta:
model = Article
def clean_name(self):
return self.cleaned_data["name"].upper()
If your goal is to always have the value in uppercase, then you should override save in the model field:
class Blog(models.Model):
name = models.CharField(max_length=100)
def save(self, force_insert=False, force_update=False):
self.name = self.name.upper()
super(Blog, self).save(force_insert, force_update)
Updated example from documentation suggests using args, kwargs to pass through as:
Django will, from time to time, extend the capabilities of built-in
model methods, adding new arguments. If you use *args, **kwargs in
your method definitions, you are guaranteed that your code will
automatically support those arguments when they are added.
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super(Blog, self).save( *args, **kwargs) # Call the "real" save() method.
do_something_else()
you have to override save(). An example from the documentation:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, force_insert=False, force_update=False):
do_something()
super(Blog, self).save(force_insert, force_update) # Call the "real" save() method.
do_something_else()

Categories

Resources