I have this manager:
class ConfigValueManager(models.Manager):
def get(self, key):
config_value = self.filter(key=key).first()
if config_value:
type_caster = locate(config_value.type)
return type_caster(config_value.value)
return config_value
def set(self, key, value):
self.filter(key=key).update(value=value)
def set2(self, key, value):
qs = self.filter(key=key)
if qs:
qs.update(value=value, type=type(value).__name__, company=self.instance)
else:
self.create(key=key, value=value, type=type(value).__name__, company=self.instance)
the problem is that I can't overwrite set. The method is still coming from the parent, even though I've created set on the child. Funny thing is that get and set2 are fine. Even add which isn't in my example can't be overridden.
My question is how can I overwrite set and why this happens?
I add some details on why it's not easily possible, because I struggled on the same issue.
set, like add or create, are overridden in the dynamically created RelatedManager, as we can see in the django source code. This RelatedManager actually uses our manager as a super class that's why your get and set2 methods can used, but it does not help for overridden methods.
This manager is created in the ReverseManyToOneDescriptor.related_manager_cls cached property. In the example on your github snippet, Company.config_values is an instance of this ReverseManyToOneDescriptor.
I'll show an example on how to override the set method, by making some assumptions on your code, because it misses some definitions (like the Company model, the ForeignKey field inside FooConfigValue.)
I don't advise to use it, as it's absolutely not robust against django changes, and I didn't do any test, it just serves as a proof on how RelatedManager instances are created
Add this at the end of the example code and it should work
def modify_related_manager_set(model_cls):
# model_cls = Company here, and config_values is the related field name
reverse_descriptor = model_cls.config_values
base_set = reverse_descriptor.related_manager_cls.set
def custom_set(*args, **kwargs):
print("in my custom set")
return base_set(*args, **kwargs)
reverse_descriptor.related_manager_cls.set = custom_set
# do this call after all the models have been created
# e.g. after defining FooConfigValue
modify_related_manager_set(Company)
And you should now see the in my custom set being printed.
I know this doesn't help much, but at least it helped understand how related fields work
models.py
from django.db import models
from django.db.models.query import QuerySet
class PersonQuerySet(QuerySet):
def set(self, slug, **kwargs):
return self.filter(slug=slug).update(**kwargs)
class Person(models.Model):
name = models.CharField(max_length=100, null=True)
slug = models.CharField(max_length=10, null=True)
objects = PersonQuerySet.as_manager()
tests.py
from django.test import TestCase
from core.models import Person
class TestSet(TestCase):
def test_just_update_records_with_the_same_slug(self):
Person.objects.create(slug='batman', name='John')
Person.objects.create(slug='batman', name='Connor')
Person.objects.create(slug='bruce', name='Ill be back')
Person.objects.set('batman', name='###')
expected_value = 2
result = Person.objects.filter(name='###').count()
self.assertEqual(result, expected_value)
github example
https://github.com/luivilella/django-test-manager
Related
I'm using Django with the REST Framework. In a serializer, I would like to assign a field value based on a view or request (request.data['type']) parameter, so I need the view/request in the context.
I succeeded, but only in a cumbersome way, and I am looking into ways to simplify the code. Here's the successful approach (omitting irrelevant fields):
class TypeDefault(object):
def set_context(self, serializer_field):
view = serializer_field.context['view'] # or context['request']
self.type = view.kwargs['type'].upper()
def __call__(self):
return self.type
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=serializers.CreateOnlyDefault(TypeDefault()))
class Meta:
model = RRset
fields = ('type',)
read_only_fields = ('type',)
To simplify things, I tried removing the TypeDefault class, and replacing the type serializer field by
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context.get('view').kwargs['type'].upper() # also tried self._context
However, context.get('view') returns None. I am unsure why the view context is not available here. My impression is that it should be possible to get the desired functionality without resorting to an extra class.
As a bonus, it would be nice to specify the default in the field declaration itself, like
type = serializers.CharField(default=self.context.get('view').kwargs['type'].upper())
However, self is not defined here, and I'm not sure what the right approach would be.
Also, I am interested if there is any difference in retrieving information from the view or from the request data. While the context approach should work for both, maybe there's a simpler way to get the CreateOnlyDefault functionality when the value is obtained from request data, as the serializers deals with the request data anyways.
Edit: Per Geotob's request, here is the code of the view that calls the serializer:
class RRsetsDetail(generics.ListCreateAPIView):
serializer_class = RRsetSerializer
# permission_classes = ... # some permission constraints
def get_queryset(self):
name = self.kwargs['name']
type = self.kwargs.get('type')
# Note in the following that the RRset model has a `domain` foreign-key field which is referenced here. It is irrelevant for the current problem though.
if type is not None:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk, type=type)
else:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)
In urls.py, I have (among others):
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets'),
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/(?P<type>[A-Z]+)/$', RRsetsDetail.as_view(), name='rrsets-type'),
SerializerMethodField is a read-only field so I do not think it will work unless you set a default value... and you are back to the same problem as with CharField.
To simply things you could get rid of serializers.CreateOnlyDefault:
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=TypeDefault())
If you want something more dynamic, I can only think of something like this:
class FromContext(object):
def __init__(self, value_fn):
self.value_fn = value_fn
def set_context(self, serializer_field):
self.value = self.value_fn(serializer_field.context)
def __call__(self):
return self.value
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True,
default=FromContext(lambda context: context.get('view').kwargs['type'].upper()))
FromContext takes a function during instantiation that will be used to retrieve the value you want from context.
All in all, your second approach above is the correct one:
Use serializers.SerializerMethodField and access self.context from the serializer method:
class SomeSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context['view'].kwargs['type'].upper()
The view, request and format keys are automatically added to your serializer context by all of the DRF generic views (http://www.django-rest-framework.org/api-guide/generic-views/#methods at the end of the section). This works just fine.
If you are creating a serializer instance manually, you will have to pass context=contextDict as an argument, where contextDict is whatever you need it to be (http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context).
As #Michael has pointed out in another answer, the SerializerMethodField will be read only. But going by your first example (type = serializers.CharField(read_only=True.....) this seems to be what you want.
I have plenty of Hardware models which have a HardwareType with various characteristics. Like so:
# models.py
from django.db import models
class HardwareType(model.Models):
name = models.CharField(max_length=32, unique=True)
# some characteristics of this particular piece of hardware
weight = models.DecimalField(max_digits=12, decimal_places=3)
# and more [...]
class Hardware(models.Model):
type = models.ForeignKey(HardwareType)
# some attributes
is_installed = models.BooleanField()
location_installed = models.TextField()
# and more [...]
If I wish to add a new Hardware object, I first have to retrieve the HardwareType every time, which is not very DRY:
tmp_hd_type = HardwareType.objects.get(name='NG35001')
new_hd = Hardware.objects.create(type=tmp_hd_type, is_installed=True, ...)
Therefore, I have tried to override the HardwareManager.create() method to automatically import the type when creating new Hardware like so:
# models.py
from django.db import models
class HardwareType(model.Models):
name = models.CharField(max_length=32, unique=True)
# some characteristics of this particular piece of hardware
weight = models.DecimalField(max_digits=12, decimal_places=3)
# and more [...]
class HardwareManager(models.Manager):
def create(self, *args, **kwargs):
if 'type' in kwargs and kwargs['type'] is str:
kwargs['type'] = HardwareType.objects.get(name=kwargs['type'])
super(HardwareManager, self).create(*args, **kwargs)
class Hardware(models.Model):
objects = HardwareManager()
type = models.ForeignKey(HardwareType)
# some attributes
is_installed = models.BooleanField()
location_installed = models.TextField()
# and more [...]
# so then I should be able to do:
new_hd = Hardware.objects.create(type='ND35001', is_installed=True, ...)
But I keep getting errors and really strange behaviors from the ORM (I don't have them right here, but I can post them if needed). I've searched in the Django documentation and the SO threads, but mostly I end up on solutions where:
the Hardware.save() method is overridden (should I get the HardwareType there ?) or,
the manager defines a new create_something method which calls self.create().
I also started digging into the code and saw that the Manager is some special kind of QuerySet but I don't know how to continue from there. I'd really like to replace the create method in place and I can't seem to manage this. What is preventing me from doing what I want to do?
The insight from Alasdair's answer helped a lot to catch both strings and unicode strings, but what was actually missing was a return statement before the call to super(HardwareManager, self).create(*args, **kwargs) in the HardwareManager.create() method.
The errors I was getting in my tests yesterday evening (being tired when coding is not a good idea :P) were ValueError: Cannot assign None: [...] does not allow null values. because the subsequent usage of new_hd that I had create()d was None because my create() method didn't have a return. What a stupid mistake !
Final corrected code:
class HardwareManager(models.Manager):
def create(self, *args, **kwargs):
if 'type' in kwargs and isinstance(kwargs['type'], basestring):
kwargs['type'] = HardwareType.objects.get(name=kwargs['type'])
return super(HardwareManager, self).create(*args, **kwargs)
Without seeing the traceback, I think the problem is on this line.
if 'type' in kwargs and kwargs['type'] is str:
This is checking whether kwargs['type'] is the same object as str, which will always be false.
In Python 3, to check whether `kwargs['type'] is a string, you should do:
if 'type' in kwargs and isinstance(kwargs['type'], str):
If you are using Python 2, you should use basestring, to catch byte strings and unicode strings.
if 'type' in kwargs and isinstance(kwargs['type'], basestring):
I was researching the same problem as you and decided not to use an override.
In my case making just another method made more sense given my constraints.
class HardwareManager(models.Manager):
def create_hardware(self, type):
_type = HardwareType.objects.get_or_create(name=type)
return self.create(type = _type ....)
I am generating a Django model based on an abstract model class AbstractAttr and a normal model (let's say Foo).
I want my foo/models.py to look like this:
from bar.models import Attrs
# ...
class Foo(models.Model):
....
attrs = Attrs()
In the Attrs class which mimics a field I have a contribute_to_class that generates the required model using type(). The generated model c is called FooAttr.
Everything works. If I migrate, I see FooAttr appear in the proper table.
EXCEPT FOR ONE THING.
I want to be able to from foo.models import FooAttr. Somehow my generated FooAttr class is not bound to the models.py file in which it is generated.
If I change my models.py to this:
class Foo(models.Model):
# ...
FooAttr = generate_foo_attr_class(...)
it works, but this is not what I want (for example, this forces the dev to guess the generate class name).
Is what I want possible, define the class somewhat like in the first example AND bind it to the specific models.py module?
The project (pre-Alpha) is here (in develop branch):
https://github.com/zostera/django-mav
Some relevant code:
def create_model_attribute_class(model_class, class_name=None, related_name=None, meta=None):
"""
Generate a value class (derived from AbstractModelAttribute) for a given model class
:param model_class: The model to create a AbstractModelAttribute class for
:param class_name: The name of the AbstractModelAttribute class to generate
:param related_name: The related name
:return: A model derives from AbstractModelAttribute with an object pointing to model_class
"""
if model_class._meta.abstract:
# This can't be done, because `object = ForeignKey(model_class)` would fail.
raise TypeError("Can't create attrs for abstract class {0}".format(model_class.__name__))
# Define inner Meta class
if not meta:
meta = {}
meta['app_label'] = model_class._meta.app_label
meta['db_tablespace'] = model_class._meta.db_tablespace
meta['managed'] = model_class._meta.managed
meta['unique_together'] = list(meta.get('unique_together', [])) + [('attribute', 'object')]
meta.setdefault('db_table', '{0}_attr'.format(model_class._meta.db_table))
# The name of the class to generate
if class_name is None:
value_class_name = '{name}Attr'.format(name=model_class.__name__)
else:
value_class_name = class_name
# The related name to set
if related_name is None:
model_class_related_name = 'attrs'
else:
model_class_related_name = related_name
# Make a type for our class
value_class = type(
str(value_class_name),
(AbstractModelAttribute,),
dict(
# Set to same module as model_class
__module__=model_class.__module__,
# Add a foreign key to model_class
object=models.ForeignKey(
model_class,
related_name=model_class_related_name
),
# Add Meta class
Meta=type(
str('Meta'),
(object,),
meta
),
))
return value_class
class Attrs(object):
def contribute_to_class(self, cls, name):
# Called from django.db.models.base.ModelBase.__new__
mav_class = create_model_attribute_class(model_class=cls, related_name=name)
cls.ModelAttributeClass = mav_class
I see you create the model from within models.py, so I think you should be able to add it to the module's globals. How about this:
new_class = create_model_attribute_class(**kwargs)
globals()[new_class.__name__] = new_class
del new_class # no need to keep original around
Thanks all for thinking about this. I have updated the source code of the project at GitHub and added more tests. See https://github.com/zostera/django-mav
Since the actual generation of the models is done outside of foo/models.py (it takes place in mav/models.py, it seems Pythonically impossible to link the model to foo/models.py. Also, after rethinking this, it seems to automagically for Python (explicit is better, no magic).
So my new strategy is to use simple functions, a decorator to make it easy to add mav, and link the generated models to mac/attrs.py, so I can universally from mav.attrs import FooAttr. I also link the generated class to the Foo model as Foo._mav_class.
(In this comment, Foo is of course used as an example model that we want to add model-attribute-value to).
I have been using ActiveState Komodo for a while and while most of the code-completion is spot on it lacks the code completion from Django's model manager.
I have included the Django directory in my PYTHONPATH and get most of the code completion, the notable exception being the models.
Assuming I have a model users I would expect the code users.objects. to show autocomplete options such as all(),count(),filter() etc. however these are added by the model's manager which does so in a seemingly abnormal way.
I am wondering if I can 'force' Komodo to pick up the models.
The model manager looks to be included from the following code (taken from manager.py)
def ensure_default_manager(sender, **kwargs):
"""
Ensures that a Model subclass contains a default manager and sets the
_default_manager attribute on the class. Also sets up the _base_manager
points to a plain Manager instance (which could be the same as
_default_manager if it's not a subclass of Manager).
"""
cls = sender
if cls._meta.abstract:
return
if not getattr(cls, '_default_manager', None):
# Create the default manager, if needed.
try:
cls._meta.get_field('objects')
raise ValueError("Model %s must specify a custom Manager, because it has a field named 'objects'" % cls.__name__)
except FieldDoesNotExist:
pass
cls.add_to_class('objects', Manager())
cls._base_manager = cls.objects
...
Specifically the last two lines. Is there any way to tell Komodo that <model>.objects = Manager() so the proper code completion is shown?
Probably the easiest way to get this to work seems to be to add the following to the top of models.py:
from django.db.models import manager
and then under each model add
objects = manager.Manager()
so that, for example, the following:
class Site(models.Model):
name = models.CharField(max_length=200)
prefix = models.CharField(max_length=1)
secret = models.CharField(max_length=255)
def __unicode__(self):
return self.name
becomes
class Site(models.Model):
name = models.CharField(max_length=200)
prefix = models.CharField(max_length=1)
secret = models.CharField(max_length=255)
objects = manager.Manager()
def __unicode__(self):
return self.name
This is how you would (explicitly) set your own model manager, and by explicitly setting the model manager (to the default) Kommodo picks up the code completion perfectly.
Hopefully this will help someone :-)
I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.
This is an excerpt from my model:
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class
With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error
No form field implemented for <class 'djangotoolbox.fields.ListField'>
So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:
class MyClassForm(ModelForm):
field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
class Meta:
model = MyClass
then I pass MyClassForm as the form to use to the admin interface
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
admin.site.register(MyClass, MyClassAdmin)
I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.
OK, here is what I did to get this all working ...
I'll start from the beginning
This is what what my model looked like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:
class ListFormField(forms.Field):
""" A form field for being able to display a djangotoolbox.fields.ListField. """
widget = ListWidget
def clean(self, value):
return [v.strip() for v in value.split(',') if len(v.strip()) > 0]
But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:
Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).
So you have your models like:
class SomeClass(models.Model):
another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
#fields go here
class AnotherClass(models.Model):
#fields go here
And in your form:
class SomeClassForm(forms.ModelForm):
#Empty field, will be populated after form is initialized
#Otherwise selection list is not refreshed after new entities are created.
another_class = forms.MultipleChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(SomeClassForm,self).__init__(*args, **kwargs)
self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]
if self.instance.pk: #If class is saved, highlight the instances that are related
self.fields['another_class'].initial = self.instance.another_class_ids
def save(self, *args, **kwargs):
self.instance.another_class_ids = self.cleaned_data['another_class']
return super(SomeClassForm, self).save()
class Meta:
model = SomeClass
Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.
This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS
You could avoid a custom form class for such usage by inquiring for the model object
class ModelListField(ListField):
def __init__(self, embedded_model=None, *args, **kwargs):
super(ModelListField, self).__init__(*args, **kwargs)
self._model = embedded_model.embedded_model
def formfield(self, **kwargs):
return FormListField(model=self._model, **kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
widget = ListFieldWidget
def __init__(self, model=None, *args, **kwargs):
self._model = model
super(FormListField, self).__init__(*args, **kwargs)
self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]
def to_python(self, value):
return [self._model.objects.get(pk=key) for key in value]
def clean(self, value):
return value