I'm currently trying to modify the django-podcast module so the podcast's xml file is statically served instead of being generated on every request for it.
I am attempting to rewrite the channel's xml file every time an episode is modified, created, or deleted and to do so I'm using django signals. What I would like to do is something like this...
from django.db.models.signals import post_save, post_delete
from django.template.loader import render_to_string
def update_xml_file(sender, **kwargs):
f = open('channelrss.xml', 'w')
f.write(render_to_string('podcast/show_feed.html', {'object': sender.show}))
f.close()
class Show(models.Model):
...
class Episode(models.Model):
post_save.connect(update_xml_file)
post_delete.connect(update_xml_file)
...
show = models.ForeignKey(Show)
...
The problem I keep running into is that sender.show is a ReverseSingleRelatedObjectDescriptor and not an actual instance of the Show class. I also tried reloading the sender object using sender.pk as the primary key value like this...
Episode.objects.filter(pk=sender.pk)
but apparently sender.pk returns a property object and not an integer or string and I don't know how to get it's value, so I guess I have two questions.
How can I retrieve the instance of Show associated with the Episode? and what the heck is a property object and why does sender.pk return it?
Thanks ahead of time for your response!
Josh
You can try:
def update_xml_file(sender, instance=False, **kwargs):
f = open('channelrss.xml', 'w')
f.write(render_to_string('podcast/show_feed.html', {'object': instance.show}))
f.close()
when instance.show.name_field is name_field of the model.
I finally figured it out! This issue was due to my lack of knowledge on the arguments being sent to my signal handler.
The sender argument sent to my handler was actually a class object and not the instance itself. In order to retrieve the instance itself I needed to use kwargs['instance'] and in order to retrieve the Show instance I simply used kwargs['instance'].show
As a result I think I understand where the property object issue was coming from. Correct me if I'm wrong, but when trying to access a 'class' object instead of an 'instance of a class' object the properties aren't defined as string or integer values, but rather property objects that need to be defined.
Also as an additional note, the signal.connect() function doesn't need to be defined in the class's model and the way it is written above is somewhat deceiving. The way it's connected above will listen for any object's save or delete signal sent. In order to associate the function with signal's from only Episode objects I should have written it as...
post_save.connect(update_xml_file, sender=Episode)
post_delete.connect(update_xml_file, sender=Episode)
or by using a decorator as shown in Django's signal documentation.
Thanks again for all the help!
Josh
Related
I am trying to dynamically add an attribute to at runtime using the following snippets of code:
View
...
for appellation in queryset:
if appellation.id in used_id_set:
appellation.is_used_flag()
print(appellation.is_used)
# prints true as expected
else:
appellation.is_not_used_flag()
first = queryset.first()
print(first.is_used)
# prints AttributeError: 'Appellation' object has no attribute 'is_used'
In Model
...
def is_used_flag(self):
self.is_used = True
def is_not_used_flag(self):
self.is_used = False
Why does it work correctly when in the loop but when I try to retrieve the attribute from an instance after it does not work? I have run into the same issue using setattr, appellation.is_used = True and modifying __dict__. Also is there a better way to do this?
I have referenced these posts:
Why can't you add attributes to object in python? I do have a dict but it does not seem to "persist" after the loop
How to assign a new class attribute via __dict__? Same issue as mentioned above
Dynamically defining instance fields in Python classes Same as above
Update
Since both answers mention similar things, I should clarify what my intentions are. I do not want to actually persist the value in the DB. I just want to serialize it and use it in the front end.
The Queryset API in django (often) returns other querysets, which are in turn evaluated by accessing the database. By doing queryset.first() you're executing another database call, where your attributes have not been yet set.
If you need to save this is_used flag between querysets (persist the change), I suggest you add a BooleanField on your model, or perhaps find another way to do what you want, as in memory attributes will not get returned by using the queryset API.
If you want the change to persist you will need to call self.save() after setting is_used, assuming that is_used is a field on the Appellation model.
models.py
from django.db import models
class Appellation(models.Model):
# ... rest of code ...
is_used = models.BooleanField(default=False)
def is_used_flag(self):
self.is_used = True
self.save()
def is_not_used_flag(self):
self.is_used = False
self.save()
Note that Django instances are still Python objects so adding an attribute dynamically will work in the same way, this is why it prints True as expected in the code you provided.
Is it possible to use self as a reference in the __init__ method when the object is not instantiated yet?
What I'm trying to do is :
class MyClass(models.Model)
__init__(self):
some_attributes = AnotherClass.objects.filter(foreignkey=self)
The thing is that as the instance of MyClass is not registered in db yet, I have an exception like "MyClass has not attribute id"
I tried to add
if self.pk:
but it doesn't work. Is there a method like
if self.is_saved_in_db():
#some code
or do I have to created this one ?
EDIT
To be more specific, I'll give an example. I have a generic class which I try to hydrate with attributes from another Model.
class MyClass(models.Model)
_init__(self):
self.hydrate()
def hydrate(self):
# Retrieving the related objects
attributes = Information.objects.filter(...)
for attr in attributes:
attribute_id = attr.name.lower().replace(" ","_")
setattr(self,attribute_id,attr)
By doing so, I can access to attributes with MyClass.my_attribute.
For a small example, if we replace MyClass by Recipe and Information with Ingredients I can do :
pasta_recipe.pasta
pasta_recipie.tomato
pasta_recipie.onions
It's a simple parsing from a foreign_key to an attribute
By writing it, I realise that it's a bit useless because I can directly use ForeignKey relationships. I think I'll do that but for my own culture, is it possible do the filter with self as attribute before database saving ?
Thanks!
This is a very strange thing to do. I strongly recommend you do not try to do it.
(That said, the self.pk check is the correct one: you need to provide more details than "it doesn't work".)
I first tried to override the delete() method but that doesn't work for QuerySet's bulk delete method. It should be related to pre_delete signal but I can't figure it out. My code is as following:
def _pre_delete_problem(sender, instance, **kwargs):
instance.context.delete()
instance.stat.delete()
But this method seems to be called infinitely and the program runs into a dead loop.
Can someone please help me?
If the class has foreign keys (or related objects) they are deleted by default like a DELETE CASCADE in sql.
You can change the behavior using the on_delete argument when defining the ForeignKey in the class, but by default it is CASCADE.
You can check the docs here.
Now the pre_delete signal works, but it doesn't call the delete() method if you are using a bulk delete, since its not deleting in a object by object basis.
In your case, using the post_delete signal instead of pre_delete should fix the infinite loop issue. Due to a ForeignKey's on_delete default value of cascade, using pre_delete logic this way will trigger the instance.context object to call delete on instance, which will then call instance.context, and so forth.
Using this approach:
def _post_delete_problem(sender, instance, **kwargs):
instance.context.delete()
instance.stat.delete()
post_delete.connect(_post_delete_problem, sender=Foo)
Can do the cleanup you want.
If you'd like a quick one-off to delete an instance and all of its related objects and those related objects' objects and so on without having to change the DB schema, you can do this -
def recursive_delete(to_del):
"""Recursively delete an object, all of its protected related
instances, those instances' protected instances, and so on.
"""
from django.db.models import ProtectedError
while True:
try:
to_del_pk = to_del.pk
if to_del_pk is None:
return # unsaved object
to_del.delete()
print(f"Deleted {to_del.__class__.__name__} with pk {to_del_pk}: {to_del}")
except ProtectedError as e:
for protected_ob in e.protected_objects:
recursive_delete(protected_ob)
Be careful, though!
I'd only use this to help with debugging in one-off scripts (or on the shell) with test databases that I don't mind wiping. Relationships aren't always obvious and if something is protected, it's probably for a reason.
Been trying to figure this out for a couple of hours now and have gotten nowhere.
class other(models.Model):
user = models.ForeignKey(User)
others = other.objects.all()
o = others[0]
At this point the ORM has not asked for the o.user object, but if I do ANYTHING that touches that object, it loads it from the database.
type(o.user)
will cause a load from the database.
What I want to understand is HOW they do this magic. What is the pythonic pixie dust that causes it to happen. Yes, I have looked at the source, I'm stumped.
Django uses a metaclass (django.db.models.base.ModelBase) to customize the creation of model classes. For each object defined as a class attribute on the model (user is the one we care about here), Django first looks to see if it defines a contribute_to_class method. If the method is defined, Django calls it, allowing the object to customize the model class as it's being created. If the object doesn't define contribute_to_class, it is simply assigned to the class using setattr.
Since ForeignKey is a Django model field, it defines contribute_to_class. When the ModelBase metaclass calls ForeignKey.contribute_to_class, the value assigned to ModelClass.user is an instance of django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor.
ReverseSingleRelatedObjectDescriptor is an object that implements Python's descriptor protocol in order to customize what happens when an instance of the class is accessed as an attribute of another class. In this case, the descriptor is used to lazily load and return the related model instance from the database the first time it is accessed.
# make a user and an instance of our model
>>> user = User(username="example")
>>> my_instance = MyModel(user=user)
# user is a ReverseSingleRelatedObjectDescriptor
>>> MyModel.user
<django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor object>
# user hasn't been loaded, yet
>>> my_instance._user_cache
AttributeError: 'MyModel' object has no attribute '_user_cache'
# ReverseSingleRelatedObjectDescriptor.__get__ loads the user
>>> my_instance.user
<User: example>
# now the user is cached and won't be looked up again
>>> my_instance._user_cache
<User: example>
The ReverseSingleRelatedObjectDescriptor.__get__ method is called every time the user attribute is accessed on the model instance, but it's smart enough to only look up the related object once and then return a cached version on subsequent calls.
This will not explain how exactly Django goes about it, but what you are seeing is Lazy Loading in action. Lazy Loading is a well known design pattern to defer the initialization of objects right up until the point they are needed. In your case until either of o = others[0] or type(o.user) is executed. This Wikipedia article may give you some insights into the process.
Properties can be used to implement this behaviour. Basically, your class definition will generate a class similar to the following:
class other(models.Model):
def _get_user(self):
## o.users being accessed
return User.objects.get(other_id=self.id)
def _set_user(self, v):
## ...
user = property(_get_user, _set_user)
The query on User will not be performed until you access the .user of an 'other' instance.
I want to make attributes of GAE Model properties. The reason is for cases like to turn the value into uppercase before storing it. For a plain Python class, I would do something like:
Foo(db.Model):
def get_attr(self):
return self.something
def set_attr(self, value):
self.something = value.upper() if value != None else None
attr = property(get_attr, set_attr)
However, GAE Datastore have their own concept of Property class, I looked into the documentation and it seems that I could override get_value_for_datastore(model_instance) to achieve my goal. Nevertheless, I don't know what model_instance is and how to extract the corresponding field from it.
Is overriding GAE Property classes the right way to provides getter/setter-like functionality? If so, how to do it?
Added:
One potential issue of overriding get_value_for_datastore that I think of is it might not get called before the object was put into datastore. Hence getting the attribute before storing the object would yield an incorrect value.
Subclassing GAE's Property class is especially helpful if you want more than one "field" with similar behavior, in one or more models. Don't worry, get_value_for_datastore and make_value_from_datastore are going to get called, on any store and fetch respectively -- so if you need to do anything fancy (including but not limited to uppercasing a string, which isn't actually all that fancy;-), overriding these methods in your subclass is just fine.
Edit: let's see some example code (net of imports and main):
class MyStringProperty(db.StringProperty):
def get_value_for_datastore(self, model_instance):
vv = db.StringProperty.get_value_for_datastore(self, model_instance)
return vv.upper()
class MyModel(db.Model):
foo = MyStringProperty()
class MainHandler(webapp.RequestHandler):
def get(self):
my = MyModel(foo='Hello World')
k = my.put()
mm = MyModel.get(k)
s = mm.foo
self.response.out.write('The secret word is: %r' % s)
This shows you the string's been uppercased in the datastore -- but if you change the get call to a simple mm = my you'll see the in-memory instance wasn't affected.
But, a db.Property instance itself is a descriptor -- wrapping it into a built-in property (a completely different descriptor) will not work well with the datastore (for example, you can't write GQL queries based on field names that aren't really instances of db.Property but instances of property -- those fields are not in the datastore!).
So if you want to work with both the datastore and for instances of Model that have never actually been to the datastore and back, you'll have to choose two names for what's logically "the same" field -- one is the name of the attribute you'll use on in-memory model instances, and that one can be a built-in property; the other one is the name of the attribute that ends up in the datastore, and that one needs to be an instance of a db.Property subclass and it's this second name that you'll need to use in queries. Of course the methods underlying the first name need to read and write the second name, but you can't just "hide" the latter because that's the name that's going to be in the datastore, and so that's the name that will make sense to queries!
What you want is a DerivedProperty. The procedure for writing one is outlined in that post - it's similar to what Alex describes, but by overriding get instead of get_value_for_datastore, you avoid issues with needing to write to the datastore to update it. My aetycoon library has it and other useful properties included.