Is it possible to add a CustomUser(AbstractUser) to a modelAdmin menu? - wagtail

My app has a customed User. It works fine with the standard settings menu: http://localhost:8888/admin/users/
But it I create a menu with a ModelAdmin class, it gives a wrong url: http://localhost:8888/admin/users/user/
from .models import User
class UserAdmin(ModelAdmin):
model = User
The actual url: /admin/users/user/ raises a ValueError
ValueError at /admin/users/user/
invalid literal for int() with base 10: 'user'
Request Method: GET
Request URL: http://localhost:8888/admin/users/user/
Django Version: 2.0.9
Exception Type: ValueError
Exception Value:
invalid literal for int() with base 10: 'user'
Exception Location: /home/lm/proyectos/sebusca/sebusca_django/env/lib/python3.6/site-packages/django/db/models/fields/__init__.py in get_prep_value, line 947
Python Executable: /home/lm/proyectos/sebusca/sebusca_django/env/bin/python
...
Local vars
Variable Value
args ('user',)
kwargs {}
request <WSGIRequest: GET '/admin/users/user/'>
view_func <function edit at 0x7f0948213f28>
My User model:
class User(AbstractUser):
centro = models.ForeignKey('CentroEducativo', on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.email
class CentroEducativo(models.Model):
pass
It is the user model that actualy returns
django.contrib.auth.get_user_model()

Related

Serializer field for side effect model django rest framework

I have a django.db.models.Model A whose instances are created in a rest_framework.serializers.ModelSerializer from POST requests.
Depending on the data being sent in the POST, I would like to create one of several other "addon" models, let's say B or C, which I link to the original through a django.db.models.OneToOneField:
from django.db import models
class A(models.Model):
some_field = models.CharField()
class B(models.Model):
a = models.OneToOneField(A, related_name='addon', on_delete=models.CASCADE)
class C(models.Model):
a = models.OneToOneField(A, related_name='addon', on_delete=models.CASCADE)
What I would like to is to have a serializer which validates the incoming data, including some string indicating which addon to use. The serializer then creates the model instance of A and based on this creates the addon model.
I do not want to create a utility field in model A used to determine which addon to use, I would like to create the model directly using the instance of model A and information from the POST itself.
At the same time when accessing the data through a get, I would like to return the original string used to determine which addon to use.
What I have come up with so far:
from rest_framework import serializers
str2model = {'b': B, 'c': C}
class AddonField(serializers.Field):
def to_representation(self, value):
# I completely ignore "value" as no "internal value" is set in "to_internal_value"
myvalue = self.parent.instance.addon
for addon_name, addon_class in str2model.items():
if isinstance(myvalue, addon_class):
return addon_name
def to_internal_value(self, data):
# I create the "internal value" after "A" instance is created, thus here I do nothing?
return data
class ASerializer(serializers.ModelSerializer):
some_field = serializers.CharField()
the_addon = AddonField()
def validate_the_addon(self, value): # here addon is a string
if value in str2model.keys():
return value
def create(self, validated_data):
addon_name = validated_data.pop('the_addon')
addon_class = str2model[addon]
a = super(ASerializer, self).create(validated_data)
addon_class.objects.create(a=a)
return a
class Meta:
model = A
fields = ["some_field", "the_addon"]
When testing this I get:
AttributeError: Got AttributeError when attempting to get a value for field `the_addon` on serializer `ASerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `A` instance.
Original exception text was: 'A' object has no attribute 'the_addon'.
How can I temporarily store the_addon in the serializer until the A instance has been created?
This is how I would typically approach it
# Serializer
class ASerializer(serializers.Serializer):
some_field = serializers.CharField()
addon_b = serializers.CharField(required=False, allow_null=True)
addon_c = serializers.CharField(required=False, allow_null=True)
def create(self, validated_data):
addon_b = validated_data.pop('addon_b')
addon_c = validated_data.pop('addon_c')
a = A.objects.create(some_field=validated_data['some_field'])
if addon_b:
B.objects.create(a=a)
if addon_c:
C.objects.create(a=a)
return a
You can do other validations if necessary.
class TestAPIView01(generics.CreateAPIView):
permission_classes = {}
serializer_class = serializers.ASerializer
queryset = A.objects.all()
Also, look at the related_name on B and C you may want to consider making them different, as that might throw an error in the future. Cheers

Allowing Edit to editable=False Fields in Django Admin

DRF will use the editable=False on a field to default the Serializer to read-only. This is a very helpful / safe default that I take advantage of (ie I won't forget to set the Serializer to read-only). That being said once I have set editable=False is there any way to then force the Django admin to allow editing one of those fields?
Presumably the admin is a super user and I do want him to be able to change the fields value but fore safety I want the default Serializer logic to be read only.
UPDATE
I don't actually need to be able to edit the field as much as "set-it" when I create the object.
You are going about this the wrong way.
Your models should be the most pure implementation of the things you are modelling. If something about a model is fixed (for example a creation date) it shouldn't be editable in the model, if its mutable, then leave as editable in the model.
Otherwise, in the future you (or someone else) might be stuck wondering why a field which is set to editable=False is some how being changed. Especially as the documentation states:
If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation.
If you have one view in which it shouldn't be editable (such as in the API), then override it there.
If you have multiple serilaizers for a model, instead make an abstract serializer with a read_only_fields set and then subclass that. For example:
class AbstractFooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
read_only_fields = ('bar',)
class MainFooSerializer(AbstractFooSerializer):
pass
class DifferentFooSerializer(AbstractFooSerializer):
pass
If you really, really want to use editable=False, but allow the item to be edited in the Admin site only on creation you have an up hill battle.
Probably the best approach would be to reimplement the AdminForm you are using for the Admin
So instead of:
class FooAdmin(admin.ModelAdmin):
Use:
class FooAdmin(admin.ModelAdmin):
form = MySpecialForm
Then declare the form:
class MySpecialForm(forms.Model):
def __init__(self, *args, **kwargs):
self.is_new = False
if kwargs.get('instance',None) is None:
# There is no instance, thus its a new item
self.is_new = True
self.fields['one_time_field'] = forms.CharField() # Or what have you.
super(MySpecialForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
instance = super(MySpecialForm, self).save(commit)
if self.is_new:
instance.your_one_time_only_field = self.one_time_field
instance.save()
return instance
Note: you will need to manually add a field and save each readonly field that you want to do this for. This may or may not be 100% functional.
For those who want to allow editing of a non-editabled field only during creation (no instance.pk, yet):
# models.py
class Entity(Model):
name = CharField(max_length=200, unique=True, null=False, blank=False, editable=False)
# admin.py
#register(Entity)
class EntityAdmin(ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if obj: # This is the case when obj is already created i.e. it's an edit
return ['id', 'name']
else:
return []
# this override prevents that the new_name field shows up in the change form if it's not a creation
def get_form(self, request, obj=None, **kwargs):
orig_self_form = self.form
if not obj:
self.form = CreateEntityForm
result = super().get_form(request, obj=obj, **kwargs)
self.form = orig_self_form
return result
# forms.py
class CreateEntityForm(ModelForm):
new_name = CharField(max_length=200, min_length=2, label='Name', required=True)
def clean_new_name(self):
code = self.cleaned_data['new_name']
# validate uniqueness - if you need
exists = Entity.objects.filter(name=code).first()
if exists:
raise ValidationError('Entity with this name already exists: {}', exists)
return name
def save(self, commit=True):
if self.instance.pk:
raise NotImplementedError('Editing of existing Entity is not allowed!')
self.instance.name = self.cleaned_data['new_name'].upper()
return super().save(commit)
class Meta:
model = Entity
fields = ['new_name']
exclude = ['id', 'name']

Customizing Profile Model in Pinax: Integrity error in admin when OnetoOneField is added

I'm new to Pinax and Django. I'm trying to extend the Pinax Profile model by having a OneToOneField that pulls from another application I plugged in (in this case, django-swingtime : http://code.google.com/p/django-swingtime/). I've got all of my models to show up in the django admin interface, but I cannot add new Users (which I want to do in the process of adding new Profiles). I get the following error:
IntegrityError at /admin/auth/user/add/
profiles_profile.event_type_id may not be NULL
Request Method: POST
Request URL: http://localhost:8000/admin/auth/user/add/
Django Version: 1.3.1
Exception Type: IntegrityError
Exception Value:
profiles_profile.event_type_id may not be NULL
My Pinax version is 0.9a2. EventType is a model from django-swingtime. I get this error when I'm trying to add a User from anywhere within the Django admin.
Here's the my Profiles/models.py (changed lines have comments next to them)
from django.db import models
from django.utils.translation import ugettext_lazy as _
from idios.models import ProfileBase
from swingtime.models import EventType #ADDED HERE
class Profile(ProfileBase):
name = models.CharField(_("name"), max_length=50, null=True, blank=True)
about = models.TextField(_("about"), null=True, blank=True)
location = models.CharField(_("location"), max_length=40, null=True, blank=True)
website = models.URLField(_("website"), null=True, blank=True, verify_exists=False)
event_type = models.OneToOneField(EventType) #ADDED HERE
def __unicode__(self):
return "Name: %s -- %s" % (self.name, self.about) #ADDED HERE
Perhaps if someone could explain the relationship between accounts, profiles, and users and what files are OK to edit and which ones are inadvisable to edit (for instance, I don't think I want to be changing anything around in my Pinax site packages...), I can make some progress. Also, I assume this idios plugin is involved in the process, but the link to documentation I've found will load (http://oss.eldarion.com/idios/docs/0.1/).
Thank you!
I've solved my error, though I'm interested in other answers and additional information since some of this is speculation/still unclear to me. I think the error stems from Pinax accounts automatically creating a "blank" idios profile for every new user created (speculation). Since I had said every profile must have a OneToOne foreign field associated with it and had not allowed for this OneToOne field to be null, there was a problem.
This question describes some of the differences between the idios profile app, Pinax account and the standard django User accounts:
Difference between pinax.apps.accounts, idios profiles, and django.auth.User
What I did to solve this problem was using the Django's signalling capabilities to make sure that as soon as a Profile is generated, so is an instance of the foreign field. This is described here:
https://docs.djangoproject.com/en/1.3/topics/signals/
Make sure to read the bit about double signalling, since this has caused trouble for some other people:
https://docs.djangoproject.com/en/1.3/topics/signals/#preventing-duplicate-signals
The final modification to my code that got rid of the error was this. Note that besides adding signalling I also explicitly said that the OnetoOneField was allowed to be null.
from django.db import models
from django.utils.translation import ugettext_lazy as _
from idios.models import ProfileBase
from swingtime.models import EventType
#for signals
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(ProfileBase):
name = models.CharField(_("name"), max_length=50, null=True, blank=True)
about = models.TextField(_("about"), null=True, blank=True)
event_type = models.OneToOneField(EventType, null=True)
def __unicode__(self):
return "Name: %s -- %s" % (self.name, self.about)
def create_User_EventType(sender, instance, created, **kwargs):
print "checking creation of profile"
if created:
print "User event type is being created"
event_label = "%s_hours" % (instance.name)
print "the event label is" + event_label
EventType.objects.create(abbr=instance.name,label=event_label)
post_save.connect(create_User_EventType,sender=Profile,dispatch_uid="event_post_save_for_profile")

GAE simple data model error '_ReverseReferenceProperty' object has no attribute 'fetch'

I have one-to-many model scheme. All seems correct, data population works but linkitem_set.fetch fails with:
AttributeError: '_ReverseReferenceProperty' object has no attribute
'fetch'
There also one question here on SO with the same error but without solution.
My code below:
class Project(db.Model):
name = db.StringProperty()
class LinkItem(db.Model):
url = db.StringProperty()
project = db.ReferenceProperty(Project)
class Show(webapp2.RequestHandler):
def get(self):
links = Project.linkitem_set.fetch(100)
self.response.headers['Content-Type'] = 'text/plain'
for li in links:
self.response.out.write(li + '/r/n')
class PopulateDb(webapp2.RequestHandler):
def get(self):
prj = Project(name = 'SomeProject 1')
prj.put()
for i in range(1000):
rlink = random.betavariate(1, 2)
link = LinkItem(url = str(rlink), project = prj)
link.put()
I'm using Python 2.7 and tested this localy and hosted.
I think that the problem is that the linkitem_set collection will only exist for an instance of Project, but you are trying to use it on the class itself.
Your code should look something more like this:
class Show(webapp2.RequestHandler):
def get(self):
prj_name = "" # Get a valid value, probably from URL params
prj_to_show = Project.all().filter("name=", prj_name).get()
if prj_to_show is not None:
links = prj_to_show.linkitem_set.fetch(100)
self.response.headers['Content-Type'] = 'text/plain'
for li in links:
self.response.out.write(li + '/r/n')

How to do custom display and auto-select in django admin multi-select field?

I'm new to django, so please feel free to tell me if I'm doing this incorrectly. I am trying to create a django ordering system. My order model:
class Order(models.Model):
ordered_by = models.ForeignKey(User, limit_choices_to = {'groups__name': "Managers", 'is_active': 1})
in my admin ANY user can enter an order, but ordered_by must be someone in the group "managers" (this is the behavior I want).
Now, if the logged in user happens to be a manager I want it to automatically fill in the field with that logged in user. I have accomplished this by:
class OrderAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "ordered_by":
if request.user in User.objects.filter(groups__name='Managers', is_active=1):
kwargs["initial"] = request.user.id
kwargs["empty_label"] = "-------------"
return db_field.formfield(**kwargs)
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This also works, but the admin puts the username as the display for the select box by default. It would be nice to have the user's real name listed. I was able to do it with this:
class UserModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.first_name + " " + obj.last_name
class OrderForm(forms.ModelForm):
ordered_by = UserModelChoiceField(queryset=User.objects.all().filter(groups__name='Managers', is_active=1))
class OrderAdmin(admin.ModelAdmin):
form = OrderForm
My problem: I can't to both of these. If I put in the formfield_for_foreignkey function and add form = OrderForm to use my custom "UserModelChoiceField", it puts the nice name display but it won't select the currently logged in user. I'm new to this, but my guess is that when I use UserModelChoiceField it "erases" the info passed in via formfield_for_foreignkey. Do I need to use the super() function somehow to pass on this info? or something completely different?
Eliminate the ModelChoiceField/ModelMultipleChoiceField subclass completely and work off the formfield_for_foreignkey method. The request argument isn't available in the subclass, and so you can't get the current user.
Then use label_from_instance method inside formfield_for_foreignkey. You can write this yourself, but a robust Django snippet is available at http://djangosnippets.org/snippets/1642/. Just subclass the class from that snippet. You can put it in a different file and import it, or just write it above the OrderAdmin class as OrderAdmin(NiceUserModelAdmin).
Lastly, rewrite the formfield_for_foreignkey method to take the kwargs["initial"] = request.user.id outside the if statement. I don't think that's necessary and I too had trouble making it work that way.
# admin.py
from django.contrib import admin
from django.contrib.auth.models import User
from (...) import Order
class NiceUserModelAdmin(admin.ModelAdmin):
# ...
class OrderAdmin(NiceUserModelAdmin):
# ...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
kwargs["initial"] = request.user.id
if db_field.name == "ordered_by":
kwargs["empty_label"] = "-------------"
return db_field.formfield(**kwargs)
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

Resources