Disable flask-admin validation for list of reference field - flask

I've made an API in flask and MongoDB (MongoEngine) and it's admin panel through flask-admin. I'm looking for a way to disable flask-admin validation for a field which is essentially list of references field.
For example:
class A(db.Document):
pid = db.IntField(unique=True)
Bs = db.ListField(db.ReferenceField(B, dbref=False, reverse_delete_rule=NULLIFY))
Here class A has a list field for references of class B. Model View for class A
class A(ModelView):
can_create = True
can_delete = True
can_edit = True
def is_accessible(self):
return current_user.has_role("admin")
Now if I try to create a new document of class A from flask-admin, it doesn't allow saying Invalid Choice. I can create a class A object only when I choose at least one B object to be referred in A.Bs. But I want to disable this validation. I want to create class A object even when initially there are no B's object are referenced.
The Code is correct and working through API. It's just flask-admin validation which is causing problem on initially creating objects of class A without linking to objects of class B.
This is possible through API but I'm finding no method to do it via admin panel. How can I disable this default validation only for this field initially or there is another method which is better?

Related

Django override model get() method won't work on foreign keys

I have a custom model defined as following, with get() method override
class CustomQuerySetManager(models.QuerySet):
def get(self, *args, **kwargs):
print('Using custom manager')
# some other logics here...
return super(CustomQuerySetManager, self).get(*args, **kwargs)
class CustomModel(models.Model):
objects = CustomQuerySetManager.as_manager()
class Meta:
abstract = True
Then I have two models defined as
class Company(CustomModel):
name = models.CharField(max_length=40)
class People(CustomModel):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
If I use get() directly like People.objects.get(pk=1) then it works, "Using custom manager" gets printed, but if I try to get the foreign key info, django still uses the get() method from the default manager, nothing gets printed and the rest of logic defined won't get executed, for example
someone = People.objects.get(id=1) # prints Using custom manager, custom logic executed
company_name = someone.company.name # nothing gets printed, custom logic does not execute
Is the foreign key field in a model using a different manager even though the foreign key model is also using my custom model class? Is there a way to make my custom get() method work for all fields?
As django doc says
By default, Django uses an instance of the Model._base_manager manager
class when accessing related objects (i.e. choice.question), not the
_default_manager on the related object
See more here.
So you have to tell django model which manager to use as base manager, like this:
class CustomModel(models.Model):
objects = CustomQuerySetManager.as_manager()
class Meta:
#django will use your custom "objects" manager as base_manager
#or you may have different managers for base and default managers
#if you define two managers with different names
base_manager_name = 'objects'
abstract = True
But, please, pay attention that you do not filter away any results from base manager. Django doc says:
This manager is used to access objects that are related to from some
other model. In those situations, Django has to be able to see all the
objects for the model it is fetching, so that anything which is
referred to can be retrieved.
Therefore, do not override get_queryset() for this kind of managers.
See more here.

Django admin update of form fields based on foreign key model selection

In django admin I have Model A with a foreign key association to Model B. Model B's values change based on the value of Model A.
When a Model B object is selected for association with a Model A object, I would like to immediately display updated values for Model B based on the current value of Model A.
I know that I can override the on_save method in the form to update the values when the user saves the form to the database. However, I would like the admin view to display the values before the user hits save.
What do I need to hook into to make this update happen?
Thank You
If you want to dinamically filter Model B values in the changeview during user interaction (that is: before submission), you need javascript:
1) after page rendering, attach a "change handler" to Model A input field
2) in that handler, call via Ajax a view to retrieve the list of values available for Model B according to the currently selected value of Model A
3) upon receiving the list, update the Model B input field accordingly
4) also, after the initial page rendering you should explicitly invoke the handler in order to have Model B input field correctly initialized
This should work for both "add" and "change" view.
I do believe that a very detailed tutorial on how to implement this procedure can be found here:
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html
The example refers to a front-end view, but can be easily adapted to the admin changeview
Let's say here what you've got for models:
# Model B
class ModelB(models.Model):
pass
# Model A
class ModelA(models.Model):
b_link = models.ForeignKey(ModelB)
I assume that you don't want to use javascript to manipulate the form but parsing it from server. In that case, what you can do is just create a preview Model B, and create the ModelForm from this model.
For example:
class ModelB(models.Model):
...
# add a method to preview B - This will not save model
def preview_b(model_a):
# update values of b according to model_a
b.derived_value = model_a.value
# file: forms.py
class ModelBForm(ModelForm):
class Meta:
model = ModelB
# file: views.py
b_model = Model.objects.all().first()
b_model.preview_b(a_model)
form = ModelBForm(instance=b_model)
This, of course, requires you to post back to server whenever choosing a new ModelA but I think that was what you wanted.

How to reference model joined across foreign key in django admin

I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.

Django - call Model save() without using "objects" Manager get_queryset

I'm developing a legacy Django 1.7 system for a client. The programmers before me overrode the Member model (basically the User model) "objects" property with a filter query that removes anything with "is_deleted" set to "True". I've listed the snippets below:
Member class snippet:
class Member(AbstractUser):
objects = MemberManager()
all_objects = models.Manager()
MemberManager class snippet:
class MemberManager(BaseUserManager):
def get_queryset(self):
return super(MemberManager, self).get_queryset().filter(is_deleted=False)
Now when I try to update a user that has the is_deleted flag set to "True" it fails. Below is an example code snippet. Notice how I use "all_objects" which is the default models.Manager() that returns all records.
user = Member.all_objects.get(pk=id) # id of an is_deleted = True record
user.is_deleted = False
user.save()
This code causes this Django query to run which unfortunately has "is_deleted = 0" included in the WHERE clause, which causes it to not find the record. Below is what shows up in the logs:
UPDATE Member [[snip...]] WHERE (Member.is_deleted = 0 AND Member.id = 6)
Is there any way to call "save()" that will not use the MemberManager.objects get_queryset filter?
I think the problem stems from having MemberManager listed first. As the documentation says:
Take note that the first Manager Django encounters (in the order in which they’re defined in the model) has a special status. Django interprets the first Manager defined in a class as the "default" Manager, and several parts of Django will use that Manager exclusively for that model. As a result, it’s a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you’d like to work with.
Reversing the order of objects and all_objects should fix the problem.
I tried Kevin Christopher Henry's answer and unfortunately changing the default manager messed up the authentication code, which needed to inherit from the "BaseUserManager" class. The solution was to create an "undelete" function in the Member model class that uses the "all_objects" property to undelete the user before it's saved.
class Member(AbstractUser):
objects = MemberManager() # default manager
all_objects = models.Manager()
def undelete(self):
if self.is_deleted:
Member.all_objects.filter(id=self.id).update(is_deleted=False)
Then in my code I did this:
user = Member.all_objects.get(pk=id)
user.undelete()

Show/Make a field editable or disabled in admin based on other field's value

I am working on a Django project and want to achieve the following functionality :-
class XYZModel(models.Model):
available = models.BooleanField(default=False)
availability_date = models.DateTimeField(null=True,blank=True)
So i want that availability_date becomes editable only if available is set to True, while giving values to the object in admin.
If available is False, availability_date is shown disabled...
How can i do so?...
You'll need to add some JavaScript to the change form for your XYZModel admin class. This can be done by overriding the change form for the template, or by adding a reference to the JavaScript file in a custom form class for your model admin.