Django admin - Override red button delete methods in edit screen - django-admin

I have two models related with a FK like this:
class ModelA(models.model):
name = models.CharField(unique=True, max_length=250)
class ModelB(models.model):
name = models.CharField(unique=True, max_length=250)
model_a = models.ForeignKey(ModelA,
on_delete=models.PROTECT)
So, to delete an instance of ModelA, I have to delete the ModelB instances related to it.
I overrided the delete_selected() method and it works when I use the admin's bulk delete feature.
But when I edit the ModelA instance, in the admin interface, and click on the red delete button in the "edit screen" it doesn't work.
Deleting the ModelA 'first-model-A' would require deleting the following protected related objects:
So I also overrided the delete_model() method:
def delete_model(self, request, obj):
# here delete ModelB instances related to ModelA instance
super(ModelAAdmin, self).delete_model(request, obj)
But, it doesn't work...
How can I override the red button delete method ?
Thank you

The point of on_delete=models.PROTECTis that it prevents deletion of Model A and it's relations, until it's relations are None. Django ORM will raise an ProtectedError. You cannot delete a Model A instance with a Model B relation.
So the question should be why are you using the protected mechanism?
But if you really need it this way... I would remove the relation of all the Model B objects referencing the Model A instance to a mock-up Model A.
Later edit:
Override the 'delete_confirmation.html' template from 'django/contrib/admin/templates/admin/delete_confirmation.html'. I just remove the "{% elif protected %}" part, but you can make to look as you want.
In the admin file have something like:
class ModelAAdmin(admin.ModelAdmin):
def delete_view(self, request, object_id, extra_context=None):
self.delete_confirmation_template = "dummy/admin/dummy/modela/delete_confirmation.html"
return super(ModelAAdmin, self).delete_view(request, object_id, extra_context)
def delete_model(self, request, obj):
for modelb_obj in obj.modelb_set.all():
modelb_obj.delete()
return super(ModelAAdmin, self).delete_model(request, obj)
admin.site.register(ModelA, ModelAAdmin)
Second edit:
Maybe you can try something like:
def delete_view(self, request, object_id, extra_context=None):
self.delete_confirmation_template = "dummy/admin/dummy/modela/delete_confirmation.html"
if request.method == 'POST':
ModelB.objects.filter(model_a__id=object_id).delete()
return super(ModelAAdmin, self).delete_view(request, object_id, extra_context)

Related

How to use an argument's value to build a `.collection` method for an associated model?

I'm not sure what the correct terminology is for my question, but is it possible to use an argument's value to "build" its appropriate .collection method?
For instance, a Submission has_many images, tags, and documents. And depending on what the user is interacting with, I'd like to create the appropriate association.
Initially, I had it set up for only the tags...
def add_to_submission(task, user, tag)
sub = Submission.find_or_create_by(task: task, user: user)
sub.tags << tag
end
But is there a way I can generalize it further so that the third argument can by more dynamic? So rather than only accept a tag, it could be used like
add_to_submission(#task, #current_user, #new_image)
Something along the lines of...
def add_to_submission(task, user, associated_item)
sub = Submission.find_or_create_by(task: task, user: user)
items = associated_item.pluralize
sub.items << associated_item
end
For dynamic calling of methods .send can be used. You can pass a symbol of the method name. You should be able to do something like this, but I would make sure to have good unit tests for your method.
def add_to_submission(task, user, associated_item)
submission = Submission.find_or_create_by(task: task, user: user)
children = associated_item.pluralize.to_sym
submission.send(children) << associated_item
end

Accessing column stored in a join table - Rails

I have a join table called ProductFeatures which joins Product and Feature instances via has_many: ..., through: product_features, and has an additional column called rating.
I want to add .rating method on Feature which will return a rating float based on specific product instance that is calling it. Something like:
Product.find(...).features.first.rating #=> should specific product_feature rating
I've tried:
passing caller_id as an argument to .rating. This works, but makes me use product.id each time I want to get a specific product rating feature.
Obtaining a caller instance id from inside the method using .caller (with binding_of_caller, or vanilla Ruby), but .caller does not seem to let me get a calling instance id, and would also fail in tests as the caller would be the spec's ExampleGroup
You can get these data with other way.
I want to add #rating method on Feature which will return a rating
float based on specific product instance that is calling it. Something
like:
## Controller add this code snipped
get_feature = Product.find(...).features
rating(get_feature)
protected
def rating(get_all_feature)
all_rating = []
get_all_feature.each do |feature|
all_rating << feature.product_features.each { |u| u.rating }
end
all_rating
end
Hope this help you!

Creating semi-private urls for show action in rails app

I am building a small app, that allows users to create lists and within these lists they can add gifts that they want. So far its very similar to a ToDo list app.
I have three models:
User - Can have many Lists
List - Can have many gifts and belongs to User
Gift - Belongs to List
In my List model as well as storing the name of the list, Im also creating a unique string of letters and numbers and storing it as shared_key in the record. The code looks like this:
def create_unique_url
begin
self.shared_key = SecureRandom.urlsafe_base64(10)
end while self.class.exists?(shared_key: shared_key)
end
and ideally I want the url to look something like this app.com/public/long_string_shared_key_goes here
My main Question is, how should do I go about setting up a route to access the record at this public address.
Should I create another controller called public and have a show method there? Or should I create a public action in my llist controller and somehow manually create a route to it?
Since it's just a matter of single action I'd not suggest to redefine the #to_param, since it might affect all of your existing functionality. Still a matter of taste, mostly
routes:
resources :lists, except: [ :show ]
get '/public/:shared_key' => 'lists#show'
controller:
def show
#list = List.find_by(shared_key: params[:shared_key])
end
view:
link_to list.name, list_path(shared_key: list.shared_key)
In you case i would just alter a little the showing of the list. First in the list model redefine the to_param method
def to_param
long_string_shared_key_goes #by default this was returning the id - example...to access show a list you had to navigate to /lists/:id
end
Now you need to change the routes.rb
match 'public/:long_string_shared_key_goes', to :'lists#show', via: [:get]
Now you just have to change some find methods (if you are accessing the lists with example list.find(params[:id] you now have to take in consideration that you dont have the id in the params, but you long_string_shared_key).
Hope this answered your question.

why does klass.create(donor) always create a new donor while donors_controller#create does not

When I go to my donors_controller#create from a form in my view, it will update a record if it matches on ID or the collection of columns in my find_by_*
But if I call that create from a different controller, it always creates new records.
My donors_controller has a create method with:
def create
# need to find donor by id if given, else use find_or_create_by_blahblahblah
unless #donor = Donor.find_by_id(params[:donor][:id])
#donor = Donor.find_or_initialize_by_company_and_prefix1_and_first_name1_and_last_name1_and_address1(params[:donor])
end
if #donor.new_record?
...
else
...
end
end
My other controller has :
class_name = 'Donor'
klass = ActiveRecord.const_get(class_name)
... code to populate myarray with appropriate values
klass.create(myarray)
I am pretty sure myarray is populated with the necessary params since it creates valid records with everything in the right place. I can even run the same record through more than once and it creates duplicate (except for the Donor.id of course) records.
What am I doing wrong here?
I noticed I could do this in my other controller and it works, but why can't I call the create from the donors_controller and have it work without always creating a new record?
#klass.create(myarray)
#mydonor = klass.find_or_initialize_by_company_and_prefix1_and_first_name1_and_last_name1_and_address1(myarray)
#mydonor.save
your question is very unclear and hard to follow, so i'm not sure my answer will match your needs. It seems you're confusing a controller#create method with a model#create method.
On the model : it is a class method
create, as its name implies, instantiates a new object of the Donor class and calls save on it:
#donor = Donor.create
# is the same thing as
#donor = Donor.new
#donor.save
Active Record uses new_record? to determine if it should perform an insert or an update on the db when you call save on an instanciated record. So with create it will always be an insert since the record is inevitably new ; but if you call save on a record retrieved from the database, it will be updated.
On a controller : it is an instance method
... but does not directly manages persistence, that's the role of the model. It is an action for this controller ; it is called create for the sake of RESTful naming conventions. create is called on a controller instance when a POST request is routed to it ; its purpose is to manage that request, which often (but not always) means to create records using the appropriate model, using YourModelName.new or YourModelName.create.
so it is absolutely possible (but not advisable) to do:
class DonorsController < ApplicationController
def create
# only works well if params[:donor][:id] is nil,
# will raise an error if a record with the
# same id exists in the database
#donor = Donor.create(params[:donor])
end
end
as it is generally needed to perform several operations before saving your record, and to check if save is successful, the process is broken down :
class DonorsController < ApplicationController
def create
#donor = Donor.new(params[:donor])
# perform operations on #donor (instance of Donor class)
if #donor.save # save returns true if successfully performed, false either
# all is fine
else
# #donor could not be saved
end
end
end

update_attributes field tweaks

So I've got an edit page that has butt-load of editable fields on it...simple update...
#patient.update_attributes(params[:patient])...everything's great, except....
I've got one field out of these 20 that I need to tweak a little before it's ready for the db and it would seem I either need to do
two trips
#patient.update_attributes(params[:patient])
#patient.update_attribute( :field=>'blah')
or set them all individually
patient.update_attributes(:field1=>'asdf', :field2=>'sdfg',:field3=>'dfgh', etc...)
Am I missing a way to do this is one swoop?
What's the attribute you need to tweak? There's two ways to do this:
Either massage the params before you send them to the update_attribute method:
I'm just giving an example here if you wanted to underscore one of the values:
params[:patient][:my_tweak_attribute].gsub!(" ", "_")
#patient.update_attributes(params[:patient])
Then there's the preferred way of doing your tweaking in a before_save or before_update callback in your model:
class Patient < ActiveRecord::Base
before_update :fix_my_tweak_attribute, :if => :my_tweak_attribute_changed?
protected
def fix_my_tweak_attribute
self.my_tweak_attribute.gsub!(" ", "_")
end
end
This keeps your controller clean of code that it probably doesn't really need.
If you just need to add a new param that didn't get sent by the form you can do it in the controller like this:
params[:patient][:updated_by_id] = current_user.id
#patient.update_attributes(params[:patient])
Assuming current_user is defined for you somewhere (again, just an example)
You can create a virtual attribute for that field. Say the field is :name. You create a function in your Patient model like :
def name
self[:name] = self[:name] * 2
end
And of course, you do your things inside that function :) Instaed of self[:name], you can also use read_attribute(:name).

Resources