PyCharm doesn't understand custom Manager of model - python

I extend default model manager and add cache-specific logic to it:
class ReadOnlyManager(manager.Manager):
use_for_related_fields = True
def create(self, **kwargs):
obj = super(ReadOnlyManager, self).create(**kwargs)
cache.cache_read_only_object(obj)
...
return obj
def update(self, *args, **kwargs):
raise ReadOnlyException()
def by_id(self, object_id):
return cache.retrieve_read_only_object(self.model, object_id)
def by_lookup(self, lookup_key, lookup_value):
return cache.retrieve_read_only_object_by_lookup(self.model, lookup_key, lookup_value)
Then I created abstract model that uses it:
class ReadOnlyModel(models.Model):
class Meta:
abstract = True
objects = ReadOnlyManager()
I use it in concrete model:
class TokenType(ReadOnlyModel):
code = models.CharField(_('code'), max_length=30, unique=True)
description = models.CharField(_('description'), max_length=100)
lookups = {
'code': 'code'
}
When I tried to call method specific for custom cache, for example *by_id*:
TokenType.objects.by_id(1) # This code works
PyCharm highlights it and writes "Unresolved attribute reference" warning. When I press CMD+Space after TokenType., I see, that autocomplete box contains two objects items: one marked with function icon and have type ReadOnlyManager, second - with method icon and have type Manager.
Is it PyCharm bug? How to enable autocomlete for additional methods in custom manager?

This seems to be a problem of PyCharm. Writing an auto completion for Python is really a hard task, especially for things like Django Models, which uses Meta Classes and other nasty stuff.
However it is possible to complete, and it seems not so difficult, for your example my autocompletion ( https://github.com/davidhalter/jedi/tree/dev, work in progress, don't use it yet) is able to complete it:
Completing TokenType.objects. :
update
by_id
by_lookup
create
use_for_related_fields
Completing TokenType.:
__metaclass__
__hash__
_get_next_or_previous_in_order
__ne__
date_error_message
description
_perform_date_checks
delete
clean
objects
unique_error_message
_set_pk_val
_deferred
save_base
pk
serializable_value
full_clean
__init__
code
save
__str__
validate_unique
clean_fields
__repr__
_perform_unique_checks
__reduce__
_get_unique_checks
prepare_database_save
_get_pk_val
__eq__
lookups
_get_next_or_previous_by_FIELD
Meta
_get_FIELD_display
As far as I'm concerned, PyCharm is Closed Source, so I think you'll have to talk to the PyCharm developers.

Related

Overloading changelist_view with no changes causing errors

Overloading changelist_view and calling the super() causes attribute errors.
I'm creating a super basic dashboard and I am overriding the changelist_view method of a ModelAdmin class. However, this override is resulting in an error "NoneType object has no attribute 'has_header'." However, I'm literally not doing anything but override the changelist_view and calling the super class - I haven't made any changes yet.
In admin.py
class SummaryAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
response = super().changelist_view(request, extra_context)
The corresponding model in models.py
class Summary(Failure):
class Meta:
proxy = True
verbose_name = 'Failure Summary'
verbose_name_plural = 'Failures Summary'
This is literally the entire contents of the admin model. When I try to go to the django admin panel, I get the attribute error. As far as I can tell, this should be no different than not overriding changelist_view at all, but if I delete the override everything functions as normal.
Update: I literally copied the changelist_view code from options.py rather than calling the super class and I do not get this error, so I suspect it has something to do with how I'm calling the super class.
The problem with the above code is it lacks a return statement. changelist_view returns a response, and an overload that does not return will cause all sorts of errors. This is an easy mistake to make if you're used to languages that will not let you compile if you forget your return or it does not match the stated type.

How and why I can't override related manager method on django?

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

How to override the model.Manager.create() method in Django?

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 ....)

python django make a subclass use the parents table

Hoi, this is my first post and I am pretty new to django.
[Edit: changed "from" to "mfrom"]
I have a generic class like
class Message(models.Model):
mfrom = models.TextField()
text = models.TextField()
def beautify(self, text):
'''replace emoticons with images'''
return text
def save(self, *args, **kwargs):
self.text = self.beautify(self.text)
super(Message, self).save(*args, **kwargs)
and I have a few sources where messages are coming from that need different handling, some (like XMPP) will come from non HTTP request sources, but external scripts.
So I thought I'd make subclasses of Message for the different types like
class MessageXMPP(Message):
def load_from_XMPP(self, xmppmsg):
self.mfrom = xmppmsg.mfrom
self.text = xmppmsg.text
class MessageJSON(Message):
def load_from_JSON(self, jsonmsg):
self.mfrom = jsonmsg.mfrom
self.text = jsonmsg.text
If I now call the save() method of the above two classes django tries to save it to the MessageXMPP resp. MessageJSON tables, while I want the data to be stored in the Message table (and not have MessageXMPP resp. MessageJSON tables created at all).
Is there a way to not create a Message object and copy the values over, but have the subclasses write to the Message table?
I don't really understand why you have separate model classes here. You should really just have one class with the different methods. Even better, since these are creation methods, you should define a custom Manager which returns the instantiated Message.
However, if you insist on having separate classes, you should make them proxy models so that they do not reference their own tables:
class MessageJSON(Message):
class Meta:
proxy = True

Add django model manager code-completion to Komodo

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 :-)

Categories

Resources