Why GenericRelation fields does not work in Data Migrations(Django) - python

I want to make data migration in order to add user read post in database. There is such code:
def user_read_posts(apps, schema_editor):
User = apps.get_model("main", "User")
Post = apps.get_model("main", "Post")
Comment = apps.get_model("comments", "Comment")
comments = Comment.objects.all()
for comment in comments:
print (comment.content_object.__class__.__name__)
if isinstance(comment.content_object, Post):
comment.user.read_posts.add(comment.content_object)
class Migration(migrations.Migration):
dependencies = [
('main', '0039_auto_20160314_0906'),
]
operations = [
migrations.RunPython(user_read_posts),
]
And in line print (comment.content_object.__class__.__name__) django raise error:
AttributeError: 'Comment' object has no attribute 'content_object'
Comment model:
class GuidaComment(GenericRelationModel):
user = models.ForeignKey(GuidaUser)
text = models.TextField()
So what should I do?
Thanks.

In RunPython migration scripts apps.get_models() will get historical version of models, not the latest django models you have in source. These historical models are quite limited. Excerpt from django documentation:
historical models will not have any custom methods that you have
defined. They will, however, have the same fields, relationships,
managers (limited to those with use_in_migrations = True) and Meta
options (also versioned, so they may be different from your current
ones).
But that does not mean that you can't use latest and fully functional models by just importing them and using them. In that case you are risking that the migration script won't run correctly in the future - since latest version of model can change (e.g. model method can be renamed, deleted, logic changed etc.). So, it is possible but you need to be aware of the risk using models like this.
Content types framework is pretty old and rarely changed django contrib application and IMHO it is safe enough to use it like this.
Inspired by the answer and solution I made and use, I have made following draft I hope you will find useful:
def user_read_posts(apps, schema_editor):
User = apps.get_model("main", "User")
Post = apps.get_model("main", "Post")
Comment = apps.get_model("comments", "Comment")
from django.contrib.contenttypes.models import ContentType
comments = Comment.objects.all()
for comment in comments:
try:
# assuming that Comment has object_id field holding reference to PK
# of the referenced object
ct = ContentType.objects.get(model=comment.content_type.model,
app_label=comment.content_type.app_label)
content_object = ct.get_object_for_this_type(pk=comment.object_id)
except Exception, ex:
# TODO: can happen if some content type / model is deleted.
continue
print (content_object.__class__.__name__)
if isinstance(content_object, Post):
# TODO: maybe you will need to copy/adapt read_posts.add method's logic here
comment.user.read_posts.add(content_object)

Instead of importing ContentType as suggested in the other answer, I would use apps.get_model to get the historic model, as mentioned here.
Here's what that would look like for the OP's example:
def user_read_posts(apps, schema_editor):
User = apps.get_model("main", "User")
Post = apps.get_model("main", "Post")
Comment = apps.get_model("comments", "Comment")
for comment in Comment.objects.all():
# get historic model for the specified content_type
model = apps.get_model(
app_label=comment.content_type.app_label,
model_name=comment.content_type.model,
)
# get the object with the specified id
content_object = model.objects.get(id=comment.object_id)
if isinstance(content_object, Post):
comment.user.read_posts.add(content_object)
...
This assumes Comment has content_type and object_id fields for the generic relation, as in the generic-relations example.
Exception handling has been omitted for clarity.

Related

Django form not calling clean_<fieldname> (in this case clean_email)

I couldn't find an answer to the following question, it took me a couple of hours to find out, hence I'm adding it. I'll add my approach of solving it and the answer.
I'm following a YouTube tutorial from This person. For some reason I'm typing the same code, and I checked every single letter. Yet for some reason my cleaning functions aren't called. It's probably something simple, especially since a related question showed something similar. It's probably a framework thing that I get wrong, but I wouldn't know what it is.
Here is the relevant code.
forms.py (complete copy/paste from his Github)
from django import forms
from .models import SignUp
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField()
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['full_name', 'email']
### exclude = ['full_name']
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split('.')
# if not domain == 'USC':
# raise forms.ValidationError("Please make sure you use your USC email.")
if not extension == "edu":
raise forms.ValidationError("Please use a valid .EDU email address")
return email
# Final part is ommited, since it's not relevant.
admin.py (typed over from the tutorial)
from django.contrib import admin
# Register your models here.
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['__unicode__', 'timestamp', 'updated']
class Meta:
model = SignUp
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
After using print statements for a while and reading questions that seemed similar but eventually didn't solve my problem, I decided to look into the source of Django (idea inspired by the most similar question I could find).
Then, I decided to debug the source, since I wanted to know how Django is treating my customized function (thanks to a tutorial + SO answer). In the source I found that the customized functions were called around return super(EmailField, self).clean(value) (line 585, django/forms/fields.py, Django 1.8). When I was stepping through the code I found the critical line if hasattr(self, 'clean_%s' % name): (line 409, django/forms/forms.py, Django 1.8). I checked for the value name which was "email". Yet, the if-statement evaluated as False ((Pdb) p hasattr(self, 'clean_%s' % name)). I didn't understand why, until I figured out that the function name was not registered ((Pdb) pp dir(self)).
I decided to take a look at the whole source code repository and cross-checked every file and then I found that
class Meta:
model = SignUp
form = SignUpForm
means that form / SignUpForm were nested inside the Meta class. At first, I didn't think much of it but slowly I started to realize that it should be outside the Meta class while staying main class (SignUpAdmin).
So form = SignUpForm should have been idented one tab back. For me, as a Django beginner, it still kind of baffles me, because I thought the Meta class was supposed to encapsulate both types of data (models and forms). Apparently it shouldn't, that's what I got wrong.

Django custom registration fields

I'm becoming increasingly bewildered by the range of answers on offer to the seemingly simple problem of adding custom fields to the django-registration register form/flow. This should be a default, documented aspect of the package (not to sound ungrateful, just that it is such a well-equipped package), but solutions to the problem are dizzying.
Can anyone give me the most simple solution to getting UserProfile model data included in the default registration register page?
Update:
I eventually used Django Registration's own signals to give me this hacky fix. It is particularly ugly because, I had to use try on the POST attribute dealing with my Boolean since I found that the checkbox returned nothing if left empty.
Would appreciate any advice on improving this, or best practice.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
Thanks
What you have looks like a workable approach.
I've looked through the django-registration code, and based on the following comments in the register view I've come up with another solution. I'm not totally sure this is cleaner, but if you aren't a fan of signals this is good. This also provides a much easier avenue if you intend to make more customizations.
# from registration.views.register:
"""
...
2. The form to use for account registration will be obtained by
calling the backend's ``get_form_class()`` method, passing the
``HttpRequest``. To override this, see the list of optional
arguments for this view (below).
3. If valid, the form's ``cleaned_data`` will be passed (as
keyword arguments, and along with the ``HttpRequest``) to the
backend's ``register()`` method, which should return the new
``User`` object.
...
"""
You could create a custom backend and override those mentioned methods:
# extend the provided form to get those fields and the validation for free
class CustomRegistrationForm(registration.forms.RegistrationForm):
receive_email = forms.BooleanField(initial=True, required=False)
# again, extend the default backend to get most of the functionality for free
class RegistrationBackend(registration.backends.default.DefaultBackend):
# provide your custom form to the registration view
def get_form_class(self, request):
return CustomRegistrationForm
# replace what you're doing in the signal handler here
def register(self, request, **kwargs):
new_user = super(RegistrationBackend, self).register(request, **kwargs)
# do your profile stuff here
# the form's cleaned_data is available as kwargs to this method
profile = new_user.userprofile
# use .get as a more concise alternative to try/except around [] access
profile.receive_email = kwargs.get('receive_email', False)
profile.save()
return new_user
To use the custom backend, you can then provide separate urls. Before including the default urls, write 2 confs that point at your custom backend. Urls are tested in the order defined, so if you define these two before including the defaults, these two will capture before the default ones are tested.
url(r'^accounts/activate/(?P<activation_key>\w+)/$',
activate,
{'backend': 'my.app.RegistrationBackend'},
name='registration_activate'),
url(r'^accounts/register/$',
register,
{'backend': 'my.app.RegistrationBackend'},
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
The docs actually describe all this, but they aren't particularly accessible (no readthedocs). They are all included in the project, and I was browsing them here.
I eventually used Django Registration's own signals to give me this fix.
I will clean up the try/except flow at some point. dokkaebi also points out above that I might be able to assess the request.GET parameters for when a checkbox is left empty.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)

Django Tastypie Override URL with slug

I have a similar coce:
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<slug>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
]
Which produces an URL like:
/api/v1/nodes/<slug>/
Everything fine except that self.get_resource_uri(bundle) returns /api/v1/nodes/<id>/ and I cannot compare the current URL with the resource URI effectively.
What am I doing wrong?
Solution: working code
I implemented the proposed solution here:
https://github.com/ninuxorg/nodeshot/blob/refactoring/nodeshot/core/base/resources.py
Any additional feedback for improvement is welcome.
You could override get_resource_uri on your resource to return the correct uri. After all, the correct one is the one with the slug since that is the (first) one captured by your resource.
Update
The right way to do this is actually to skip override_urls and put this on the Resource's Meta:
detail_uri_name = 'slug'
TLDR Background
I dug a bit deeper and it looks like there's a much better place to implement this. The default implementation of get_resource_uri (indirectly) calls self.detail_uri_kwargs and then reverse.
The default implementation of detail_uri_kwargs in ModelResource just looks up self._meta.detail_uri_name (whose name is not intuitive), and grabs that key off the model. detail_uri_name defaults to pk.
If you just provide a different name here, you can skip the override_urls and the get_resource_uri!
Something like this (building on the code linked in comments by the OP):
from tastypie.resources import ModelResource
from tastypie.bundle import Bundle
class BaseSlugResource(ModelResource):
""" Base Model Resource using slug urls """
class Meta:
abstract = True
detail_uri_name = 'slug'
I'm not sure off the top of my head whether resource Metas are inherited (I'm going to guess they're not), so this may not work as a base class. Luckily, the one line required is probably fine to paste into each Resource that needs it.
I'd like to clean it up with a working example even though the #dokkaebi's answer is marked (and partially) correct. The only missing part is you still have to prepend url that will be resolved for listing and such.
from tastypie.resources import ModelResource
from myapp.models import SomeModel
class BaseSlugResource(ModelResource):
""" Base Model Resource using slug urls """
class Meta:
queryset = SomeModel.objects.all()
detail_uri_name = 'slug'
def prepend_urls(self):
return [
url(r'^(?P<resource_name>%s)/(?P<slug>[\w\.-]+)/$' % self._meta.resource_name, self.wrap_view('dispatch_detail'), name='api_dispatch_detail'),
]
This will display the correct resource_uri's for listing as well as resource get. However, you'll most likely loose {schema} resource (i.e. /api/posts/schema/) as it's treated as a slug too.

Django - Get ContentType model by model name (Generic Relations)

I'm thinking about this for a while now,
I'm creating a chat application, in chat.models a class Room is specified, however, a Room can be related to anything in my project, since it uses a generic relation in it's foreign key.
Is there a way to know which model that Room is related knowing only the models name?
Like:
ctype = 'user'
related_to_user = Room.objects.filter(content_type=ctype)
The problem I'm having is, the code below is in a view:
doc = get_object_or_404(Document, id=id)
# get *or create* a chat room attached to this document
room = Room.objects.get_or_create(doc)
If I don't want to use Document model, if I want a model associated to a string, a string that can be anything, without having to write tons of if's to get a specific Model for the specific string. Is there a way to find a model just by it's 'name'?
Thanks
http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#methods-on-contenttype-instances
user_type = ContentType.objects.get(app_label="auth", model="user")
user_type = ContentType.objects.get(model="user")
# but this can throw an error if you have 2 models with the same name.
Very similar to django's get_model
from django.db.models import get_model
user_model = get_model('auth', 'user')
To use your example exactly:
ctype = ContentType.objects.get(model='user')
related_to_user = Room.objects.filter(content_type=ctype)

Nested inlines in the Django admin?

Alright, I have a fairly simple design.
class Update(models.Model):
pub_date = models.DateField()
title = models.CharField(max_length=512)
class Post(models.Model):
update = models.ForeignKey(Update)
body = models.TextField()
order = models.PositiveIntegerField(blank=True)
class Media(models.Model):
post = models.ForeignKey(Post)
thumb = models.ImageField(upload_to='frontpage')
fullImagePath = models.ImageField(upload_to='frontpage')
Is there an easy-ish way to allow a user to create an update all on one page?
What I want is for a user to be able to go to the admin interface, add a new Update, and then while editing an Update add one or more Posts, with each Post having one or more Media items. In addition, I want the user to be able to reorder Posts within an update.
My current attempt has the following in admin.py:
class MediaInline(admin.StackedInline):
model = Media
class PostAdmin(admin.ModelAdmin):
inlines = [MediaInline,]
This let's the user add a new Post item, select the relevant Update, add the Media items to it, and hit save - which is fine. But there's no way to see all the Posts that belong to a given Update in a single place, which in turn means you can't roderder Posts within an update. It's really quite confusing for the end user.
Help?
As of now there is no "built-in" way to have nested inlines (inline inside inline) in django.contrib.admin. Pulling something like this off is possible by having your own ModelAdmin and InlineModelAdmin subclasses that would enable this kind of functionality. See the patches on this ticket http://code.djangoproject.com/ticket/9025 for ideas on how to implement this. You'd also need to provide your own templates that would have nested iteration over both the top level inline and it's child inline.
There is now this egg available, which is a collation of the relevant patches mentioned in the other answer:
https://github.com/theatlantic/django-nested-admin
I have done this using https://github.com/theatlantic/django-nested-admin, for the following Data structure:
Contest
Judges
Contestants
Singers
Songs
My admin.pyfile:
from django.contrib import admin
import nested_admin
from .models import Contest, Contestant, Judge, Song, Singer
class SongInline(nested_admin.NestedTabularInline):
model = Song
extra = 0
class SingerInline(nested_admin.NestedTabularInline):
model = Singer
extra = 0
class ContestantInline(nested_admin.NestedTabularInline):
model = Contestant
inlines = [SongInline, SingerInline]
extra = 0
class JudgeInline(nested_admin.NestedTabularInline):
model = Judge
extra = 0
class ContestAdmin(nested_admin.NestedModelAdmin):
model = Contest
inlines = [ContestantInline, JudgeInline]
extra = 0
admin.site.register(Contest, ContestAdmin)
https://github.com/theatlantic/django-nested-admin appears to be much more actively maintained than the other apps already mentioned (https://github.com/BertrandBordage/django-super-inlines and https://github.com/Soaa-/django-nested-inlines)
I have just ran into this issue as well... Seems this thread which contains the request for the nested inlines feature (https://code.djangoproject.com/ticket/9025#no2) has been updated with further information.
A custom made app called "django-super-inline" has been released. More details here: https://github.com/BertrandBordage/django-super-inlines
Installation and usage instructions below.
Hope this is useful for whomever comes across this.
I ran into a similar issue to this. My approach was to make an UpdateAdmin that held inlines for both Media and Post... it basically just makes it so you have a list of all of the media entries followed by all of the posts in an update.
class MediaInline(admin.StackedInline):
model = Media
class PostInline(admin.StackedInline):
model = Post
class PostAdmin(admin.ModelAdmin):
inlines = [MediaInline,]
class UpdateAdmin(admin.ModelAdmin):
inlines = [MediaInline,PostInline]
It isn't an ideal solution but it works for a quick and dirty work around.
Use django-nested-admin which is the best package to do nested inlines.
First, install "django-nested-admin":
pip install django-nested-admin
Then, add "nested_admin" to "INSTALLED_APPS" in "settings.py":
# "settings.py"
INSTALLED_APPS = (
# ...
"nested_admin", # Here
)
Then, add "path('_nested_ad..." to "urlpatterns" in "urls.py":
# "urls.py"
from django.urls import include, path
urlpatterns = [
# ...
path('_nested_admin/', include('nested_admin.urls')), # Here
]
Finally, extend "NestedTabularInline" with "MediaInline()" and "PostInline()" classes and extend "NestedModelAdmin" with "UpdateAdmin()" class in "admin.py" as shown below:
# "admin.py"
from .models import Media, Post, Update
from nested_admin import NestedTabularInline, NestedModelAdmin
class MediaInline(NestedTabularInline):
model = Media
class PostInline(NestedTabularInline):
model = Post
inlines = [MediaInline]
#admin.register(Update)
class UpdateAdmin(NestedModelAdmin):
inlines = [PostInline]

Categories

Resources