How do you keep a ManyToMany relationship up to date? - django

I have the following classes:
class Plugin(models.Model):
pass
class Instance(models.Model):
plugins = models.ManyToManyField(Plugin, blank=True, null=True)
My goal is to write a cron job which gets a list of plugins from this instance to reflect the instance_plugin relationship. This means that if a plugin is removed from the instance in real life then it must also be reflected here.
for instance in Instance.objects.all():
url = 'http://{}/wordpress_plugins'.format(instance.ip_address)
...
plugins = []
for path, dic in response.json().items():
plugin, created = Plugin.objects.update_or_create(
...
)
plugins.add(plugin)
instance.plugin_set = plugins
I am getting TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use plugins.set() instead.
What is the best approach to what I am doing?

Maybe this can do what you want to do.
If you edit you codes with the fields that all your models have,
I can provide you a better one.
for instance in Instance.objects.all():
url = 'http://{}/wordpress_plugins'.format(instance.ip_address)
...
plugins = []
for path, dic in response.json().items():
plugin, created = Plugin.objects.update_or_create(
...
)
plugins.add(plugin)
# First Option
# We add each plugin from the Plugins List
[instance.plugins.add(item) for item in plugins]
# We loop through the instance to see which plugin is not in the New List Plugin, we delete it
for plug in instance.plugins.all():
if plug not in plugins:
plug.delete()
# 2nd Option
# We clear the intance ManyToMany Field
instance.plugins.clear()
# We add each plugin from the Plugins List
[instance.plugins.add(item) for item in plugins]
# We loop The all The Plugin, to see which plugin does not have a reverse relation to instance
for plug in Plugin.objects.all():
if not plug.instance_set.all():
plug.delete()

Related

How to reuse template in Flask-appbuilder with exposed custom handlers?

It is a very specific question regarding Flask-appbuilder. During my development, I found FAB's ModelView is suitable for admin role, but need more user logic handlers/views for complex designs.
There is a many to many relationship between devices and users, since each device could be shared between many users, and each user could own many device. So there is a secondary table called accesses, describes the access control between devices and users. In this table, I add "isHost" to just if the user owns the device. Therefore, we have two roles: host and (regular) user. However, these roles are not two roles defined as other applications, since one man can be either host or user in same time. In a very simple application, enforce the user to switch two roles are not very convinient. That makes things worse.
Anyway, I need design some custom handlers with traditional Flask/Jinja2 templates. For example:
class PageView(ModelView):
# FAB default URL: "/pageview/list"
datamodel = SQLAInterface(Page)
list_columns = ['name', 'date', 'get_url']
#expose("/p/<string:url>")
def p(self, url):
title = urllib.unquote(url)
r = db.session.query(Page).filter_by(name = title).first()
if r:
md = r.markdown
parser = mistune.Markdown()
body = parser(md)
return self.render_template('page.html', title = title, body = body)
else:
return self.render_template('404.html'), 404
Above markdown page URL is simple, since it is a seperate UI. But if I goes to DeviceView/AccountView/AccessView for list/show/add/edit operations. I realized that I need a unique styles of UI.
So, now how can I reuse the existing templates/widgets of FAB with custom sqlalchemy queries? Here is my code for DeviceView.
class DeviceView(ModelView):
datamodel = SQLAInterface(Device)
related_views = [EventView, AccessView]
show_template = 'appbuilder/general/model/show_cascade.html'
edit_template = 'appbuilder/general/model/edit_cascade.html'
#expose('/host')
#has_access
def host(self):
base_filters = [['name', FilterStartsWith, 'S'],]
#if there is not return, FAB will throw error
return "host view:{}".format(repr(base_filters))
#expose('/my')
#has_access
def my(self):
# A pure testing method
rec = db.session.query(Access).filter_by(id = 1).all()
if rec:
for r in rec:
print "rec, acc:{}, dev:{}, host:{}".format(r.account_id, r.device_id, r.is_host)
return self.render_template('list.html', title = "My Accesses", body = "{}".format(repr(r)))
else:
return repr(None)
Besides sqlalchemy code with render_template(), I guess base_filters can also help to define custom queries, however, I have no idea how to get query result and get them rendered.
Please give me some reference code or example if possible. Actually I have grep keywords of "db.session/render_template/expoaw"in FAB's github sources. But no luck.

Updating and creating a new instance at the same time

When a user updates an invoice form, i want to create a new invoice record with the updated attributes, but also change one or two fields of the old record and save it, too.
How would the outline of a controller action look like which could accomplish this?
Instead of a controller action i put the code in the model, using callbacks:
before_save do |rec|
if !rec.new_record?
attrb = rec.attributes.delete_if{|k, v| ["id"].include? k }
Book.create(attrb)
rec.restore_attributes
rec.year = rec.year + 2 # some custom change
true
end
end
I keep all attributes unless the 'id' (otherwise i get an error) for create a new record with the new attributes.
Then i restore the attributes of the existing record. I do some custom change before saving.
I am rather new with Rails but this seems pretty straightforward. As you mention the user is 'updating" an invoice, your controller view has probably been passed all the data available to the user for further change.
When submitting the form, your update action can easily update the current record data, as well as creating a new one on top of this
Though as it is automated, you need to make clear:
if a new invoice record is created each time an invoice record is
updated (thi can create a lot of copies of the same invoice)
how you make the old record an archive to avoid duplicates
can the 'additional" amendments be automated and easily processed through an algorithm...
Nested attributes made things a bit tricky. So in order to create new instances I had to use the dup method for both the resource and its nested items.
Generally, it is advisable to keep the controllers slim and make the models fat. Nevertheless, I have decided to include this code into my Invoices controller:
def revise_save
#contact = Contact.find(params[:contact_id])
#invoice = #contact.invoices.find(params[:invoice_id])
#invoice_old = #invoice.dup
#invoice.invoice_items.each do |item|
#invoice_old.invoice_items << item.dup
end
#invoice.datum = DateTime.now.to_date
# archive old invoice
# #invoice_old. ...
#invoice_old.save
# make old new invoice
#invoice.datum = Time.now
# ...
#invoice.update(invoice_params)
redirect_to invoices_path
end
Note that in this solution the currently edited (original) invoice becomes the new invoice, the old one is paradoxically created anew.
Thanks to #iwan-b for pointing me in the right direction.

django-reversion revert ManyToMany fields outside admin

I am using django-reversion in my project.
And it works good except one thing:
I can't get previous versions of ManyToMany fields. But in django admin it is works, not in my code.
To get previous version I use following code:
vprod = Version.objects.get_for_date(product, ondate).get_object_version().object
and it works except m2m field
where 'product' is object of Product class,
class Product(models.Model):
name = models.CharField(max_length=255)
elements = models.ManyToManyField(Sku)
class Sku(models.Model):
name = models.CharField(max_length=255, verbose_name="SKU Name")
I can get vprod.name and it returns what I need, but when I try vprod.elements.all() it returns list only the current (last) version, even if the number of elements changed.
If I understand it correctly, I think you should get the revision for the version; the version contains the data of the object, the revision contains versions for multiple objects. Have a look at:
some_version.revision.version_set.all()
Concretely, I think you should use (untested):
[
v for v in Version.objects.get_for_date(product, ondate).revision.version_set.all()
if version.content_type == ContentType.objects.get_for_model(Sku)
]
Note, btw, that reversions should know that it should follow relationships. Using the low level API:
reversion.register(YourModel, follow=["your_foreign_key_field"])
I had the same issue and thanks to #Webthusiast's answer I got my working code. Adapting to your example would be something like this.
Imports:
from django.contrib.contenttypes.models import ContentType
import reversion
Register your models:
reversion.register(Sku)
reversion.register(Product, follow=['elements'])
And then you can iterate:
object = Product.objects.get(some_id)
versions = reversion.get_for_object(self.object)
for version in versions:
elements = [v.object_version.object \
for v in version.revision.version_set.all() \
if v.content_type == ContentType.objects.get_for_model(Product)]
The documentation for this is now on Read the Docs. Refer to the 'Advanced model registration' section of the Low-level API page.

Bulk create model objects in django

I have a lot of objects to save in database, and so I want to create Model instances with that.
With django, I can create all the models instances, with MyModel(data), and then I want to save them all.
Currently, I have something like that:
for item in items:
object = MyModel(name=item.name)
object.save()
I'm wondering if I can save a list of objects directly, eg:
objects = []
for item in items:
objects.append(MyModel(name=item.name))
objects.save_all()
How to save all the objects in one transaction?
as of the django development, there exists bulk_create as an object manager method which takes as input an array of objects created using the class constructor. check out django docs
Use bulk_create() method. It's standard in Django now.
Example:
Entry.objects.bulk_create([
Entry(headline="Django 1.0 Released"),
Entry(headline="Django 1.1 Announced"),
Entry(headline="Breaking: Django is awesome")
])
worked for me to use manual transaction handling for the loop(postgres 9.1):
from django.db import transaction
with transaction.atomic():
for item in items:
MyModel.objects.create(name=item.name)
in fact it's not the same, as 'native' database bulk insert, but it allows you to avoid/descrease transport/orms operations/sql query analyse costs
name = request.data.get('name')
period = request.data.get('period')
email = request.data.get('email')
prefix = request.data.get('prefix')
bulk_number = int(request.data.get('bulk_number'))
bulk_list = list()
for _ in range(bulk_number):
code = code_prefix + uuid.uuid4().hex.upper()
bulk_list.append(
DjangoModel(name=name, code=code, period=period, user=email))
bulk_msj = DjangoModel.objects.bulk_create(bulk_list)
Here is how to bulk-create entities from column-separated file, leaving aside all unquoting and un-escaping routines:
SomeModel(Model):
#classmethod
def from_file(model, file_obj, headers, delimiter):
model.objects.bulk_create([
model(**dict(zip(headers, line.split(delimiter))))
for line in file_obj],
batch_size=None)
Using create will cause one query per new item. If you want to reduce the number of INSERT queries, you'll need to use something else.
I've had some success using the Bulk Insert snippet, even though the snippet is quite old.
Perhaps there are some changes required to get it working again.
http://djangosnippets.org/snippets/446/
Check out this blog post on the bulkops module.
On my django 1.3 app, I have experienced significant speedup.
bulk_create() method is one of the ways to insert multiple records in the database table. How the bulk_create()
**
Event.objects.bulk_create([
Event(event_name="Event WF -001",event_type = "sensor_value"),
Entry(event_name="Event WT -002", event_type = "geozone"),
Entry(event_name="Event WD -001", event_type = "outage") ])
**
for a single line implementation, you can use a lambda expression in a map
map(lambda x:MyModel.objects.get_or_create(name=x), items)
Here, lambda matches each item in items list to x and create a Database record if necessary.
Lambda Documentation
The easiest way is to use the create Manager method, which creates and saves the object in a single step.
for item in items:
MyModel.objects.create(name=item.name)

django admin filter tweaking

I want to use django's admin filter on the list page.
The models I have are something like this:
class Location(model):
name = CharField()
class Inquiry(Model):
name = CharFiled()
location = ManyToManyField(Location)
Now I want to filter Inquiries, to display only those that contain relation to specific Location object. If I use
class InqAdmin(ModelAdmin):
list_filter = ['location', ]
admin.site.register(Inquiry, InqAdmin)
the admin page displays me the list of all Locations and allows to filter.
What I would like to get, is to get list of only those locations that have some Inquiries in relation to them (so I don't ever get the empty list result after filtering).
How can this be done?
You could create a custom manager for Locations that only returns Locations that have an Inquiry associated with them. If you make this the default manager, the admin will use it.
Only caveat is that you'll need create another manager that returns all Locations and use that in the rest of your app whenever you want to retrieve Locations that don't have an associated Inquiry.
The managers section in the Django docs is quite good, and should be all you need to get this set up.
EDIT:
sienf brings up a good point. Another way to accomplish this would be to define a subclass of django.contrib.admin.SimpleListFilter, and write the queryset method to filter out Inquiries with empty Locations. See https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter