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)
Related
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.
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...
I was trying to create a django form and one of my field contain a ModelChoiceField
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
When I try the code above what it return as an html ouput is
<option value="1">Person object</option>
on my Person Model I have the fields "id, fname, lname, is_active" . Is it possible to specify that my dropdown option will use "id" as the value and "lname" as the label? The expected html
should be
<option value="1">My Last Name</option>
Thanks in advance!
You can just add a call to label_from_instance in the init of Form ie
by adding something like
class TestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.fields['field_name'].label_from_instance = self.label_from_instance
#staticmethod
def label_from_instance(obj):
return "My Field name %s" % obj.name
From the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField
The __unicode__ (__str__ on Python 3) method of the model will be
called to generate string representations of the objects for use in
the field’s choices; to provide customized representations, subclass
ModelChoiceField and override label_from_instance. This method will
receive a model object, and should return a string suitable for
representing it. For example:
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
So, you can do that, or override __str__ on your model class to return the last name.
In your Person model add:
def __unicode__(self):
return u'{0}'.format(self.lname)
If you are using Python 3, then define __str__ instead of __unicode__.
def __str__(self):
return u'{0}'.format(self.lname)
You can overwrite label_from_instance method of the ModelChoiceField instance to your custom method. You can do it inside the __init__ method of the form
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['person'].label_from_instance = lambda instance: instance.name
to chose/change the value you can use "to_field_name" options ,
and to change the label of option you can overwrite the "label_from_instance" function inside ModelChoiceField class,
and here is a simple example ,
forms.py:
from django import forms
from .models import Group
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return f"My Object {obj.group_name}"
class MyPureDjangoForm(forms.Form):
group = MyModelChoiceField(queryset=Group.objects.all(),
widget=forms.Select(attrs={
'class': 'form-control'
}),
to_field_name='id',
)
for more information's kindly visit the following URL :
https://docs.djangoproject.com/en/3.2/ref/forms/fields/#django.forms.ModelChoiceField
i hope this helpful .
Similar to Thomas's answer, I recommend setting the label_from_instance method reference when creating the field. However, as I almost always want the model to have the same value for select field drop downs, I just define a get_select_field_label() method on the Model itself. This is especially useful when I want different select labels and __str__() representations. Heres a minimal example:
from django.db import models
from django import forms
class Book(models.Model):
author = models.ForeignKey("Author", on_delete=models.CASCADE)
title = models.TextField()
def __str__(self):
return f"{self.title}, by {self.author.full_name}"
def get_select_field_label(self):
return f"{self.title}"
class BookForm(forms.Form):
title = forms.ModelChoiceField(queryset=Book.objects.all(), widget=forms.Select())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label_from_instance = Book.get_select_field_label
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 = ''
I am hoping to dynamically update a ModelForm's inline Meta class from my view. Although this code seems to update the exclude list in the Meta class, the output from as_p(), as_ul(), etc does not reflect the updated Meta exclude.
I assume then that the html is generated when the ModelForm is created not when the as_*() is called. Is there a way to force the update of the HTML?
Is this even the best way to do it? I just assumed this should work.
Thoughts?
from django.forms import ModelForm
from testprogram.online_bookings.models import Passenger
class PassengerInfoForm(ModelForm):
def set_form_excludes(self, exclude_list):
self.Meta.exclude = excludes_list
class Meta:
model = Passenger
exclude = []
The Meta class is used to dynamically construct the form definition - so by the time you've created the ModelForm instance, the fields not in the exclude have already been added as the new object's attributes.
The normal way to do it would be to just have multiple class definitions for each possible exclude list. But if you want the form itself to be dynamic, you'll have to create a class definition on the fly. Something like:
def get_form(exclude_list):
class MyForm(ModelForm):
class Meta:
model = Passenger
exclude = exclude_list
return MyForm
form_class = get_form(('field1', 'field2'))
form = form_class()
UPDATE: I just revisited this post and thought I'd post a little more idiomatic way to handle a dynamic class:
def PassengerForm(exclude_list, *args, **kwargs):
class MyPassengerForm(ModelForm):
class Meta:
model = Passenger
exclude = exclude_list
def __init__(self):
super(MyPassengerForm, self).__init__(*args, **kwargs)
return MyPassengerForm()
form = PassengerForm(('field1', 'field2'))
Another way:
class PassengerInfoForm(ModelForm):
def __init__(self, *args, **kwargs):
exclude_list=kwargs.pop('exclude_list', '')
super(PassengerInfoForm, self).__init__(*args, **kwargs)
for field in exclude_list:
del self.fields[field]
class Meta:
model = Passenger
form = PassengerInfoForm(exclude_list=['field1', 'field2'])
Similar approach, somewhat different goal (generic ModelForm for arbitrary models):
from django.contrib.admin.widgets import AdminDateWidget
from django.forms import ModelForm
from django.db import models
def ModelFormFactory(some_model, *args, **kwargs):
"""
Create a ModelForm for some_model
"""
widdict = {}
# set some widgets for special fields
for field in some_model._meta.local_fields:
if type(field) is models.DateField:
widdict[field.name] = AdminDateWidget()
class MyModelForm(ModelForm): # I use my personal BaseModelForm as parent
class Meta:
model = some_model
widgets = widdict
return MyModelForm(*args, **kwargs)
Use modelform_factory (doc):
from django.forms.models import modelform_factory
from testprogram.online_bookings.models import Passenger
exclude = ('field1', 'field2')
CustomForm = modelform_factory(model=Passenger, exclude=exclude) # generates ModelForm dynamically
custom_form = CustomForm(data=request.POST, ...) # form instance