5

I have a models.py file that looks like this:

from django.db import models
from common.models import Record
from tinymce import models as tinymce_models

# Create your models here.
class Address(Record):
    def __unicode__(self):
        return unicode(self.ip)

    ip = models.IPAddressField(unique=True)
    notes = models.TextField(blank=True)

    class Meta:
        verbose_name_plural = "Addresses"

class Servers(models.Model):
    def __unicode__(self):
        return unicode(self.server_name)

    server_name = models.CharField(max_length=100)
    ip_address = models.ForeignKey(Address)
    secondary_ips = models.ManyToManyField(Address, verbose_name = 'Secondary IPs', blank=True, related_name='secondary_ips')

    class Meta:
        verbose_name_plural = "Servers"

I have a list of IP's and Servers in the system. I am trying to have the ManytoManyField only display the list of IP's that are not currently associated with a server.

I have the following queryset:

inner_qs = Servers.objects.values_list('ip_address_id', flat=True)
entries = Address.objects.exclude(id__in=inner_qs)

It returns only the IP addresses that are not in the Server table. I do not know how to incorporate those results into my ManytoManyField and where I am supposed to place my queryset. I currently only get the results when I enter the django Shell

Any ideas,

Thanks

César
  • 9,939
  • 6
  • 53
  • 74
user1607158
  • 478
  • 1
  • 4
  • 12
  • what are you trying to do with the ips that aren't attached to servers? – dm03514 Aug 17 '12 at 17:01
  • I'm trying to have those IP's show up in my filter_horizontal = ('secondary_ips',) in my admin.py file. Those should be the only IP's I can choose from. Instead, it lists all of the IP addresses in my Address table. – user1607158 Aug 17 '12 at 19:23

4 Answers4

16

If you want to add a queryset to a many to many field first change it into a list and add it as positional arguments using *

Example

# Returns a queryset
permissions = Permission.objects.all()

# Add the results to the many to many field (notice the *)

group = MyGroup.objects.get(name='test')

group.permissions.add(*permissions)
pjpassa
  • 35
  • 1
  • 8
Dr Manhattan
  • 13,537
  • 6
  • 45
  • 41
  • Two queries instead of one :( – DylanYoung Feb 27 '17 at 20:04
  • @DylanYoung if you have a way to do it with one query please do share – Dr Manhattan Feb 28 '17 at 13:55
  • Actually I was mistaken; it's three queries. Something like this does it in two, assuming you know the group pk: ThroughModel = MyGroup.permissions.through; ThroughModel.objects.bulk_create(ThroughModel(permission=permission_id, mygroup=group_pk) for permission_id in Permission.objects.all()) – DylanYoung Mar 08 '17 at 12:04
  • Bit of a cumbersome pattern. My comment was more on the Django ORM than your answer :) Still struggling with a one query solution. – DylanYoung Mar 08 '17 at 12:05
1

Returns a queryset

permissions = Permission.objects.all()

Add the results to the many to many field (notice the *)

group = MyGroup.objects.get(name='test')
group.permissions.add(*permissions)
Darth Hunterix
  • 1,484
  • 5
  • 27
  • 31
Anymnous
  • 11
  • 1
0

You can use this answer here: Filter ManyToMany box in Django Admin

In short, you need to create a custom form that extends django.forms.ModelForm. There, in __init__-method, place new options to the right widget (secondary_ips). Finally, add form = YourOwnForm to the ServerAdmin class you are using.

I suggest modifying related_names in the Servers-model:

class Servers(models.Model):
    # ...
    ip_address = models.ForeignKey(Address, related_name='server')
    secondary_ips = models.ManyToManyField(Address, verbose_name = 'Secondary IPs', \
        blank=True, related_name='servers_secondary')

Then, you can use a nice QuerySet:

allowed_addresses = Address.objects.filter(server=None)

Secondary addresses

Of course, this filters only the IPs that are as a primary IP in some server. If you want to filter out also the ips that are as a secondary_ip in some other server, things get a bit trickier:

You need to filter also on servers_secondary=None. But you can't filter out the IPs selected for the current object (the server that is edited in the admin), or those selected IPs will disappear too.

Accomplish this using Q objects, and grab the currently selected object from kwargs. Your custom Form's __init__-method will then look something like this:

def __init__(self, *args, **kwargs):
    super(YourOwnForm, self).__init__(*args, **kwargs)
    instance = kwargs.get('instance')
    allowed_addresses = Address.objects \
        .filter(server=None) \
        .filter(Q(servers_secondary=None) | Q(servers_secondary=instance))
    choices = []
    for choice in allowed_addresses:
        choices.append((choice.id, choice.ip))
    w = self.fields['secondary_ips'].widget
    w.choices = choices

Primary address dropdown menu

Folowing the same method, it is also possible to filter items in the primary IP address drop-down menu. Of course, care must be taken not to remove the selected IP address from the list.

Community
  • 1
  • 1
Mikko
  • 116
  • 3
  • Thanks Mikko. I'll try that out and let know know what happens. – user1607158 Aug 20 '12 at 13:40
  • Hi Mikko, It works if I already have Servers listed in the database by loading them in through fixtures. But if I start from a clean database, I can add IP's just fine, but when I try to add a Server I get the following error: Exception Type: KeyError Exception Value: 'instance' – user1607158 Aug 20 '12 at 14:45
  • Hi again, I was able to fix it by adding a try/except. try: instance = kwargs['instance'] except: instance = None Thanks again for your help. – user1607158 Aug 20 '12 at 14:50
  • Yes, you are right. There should be some protection in case `kwargs['instance']` is `None`. Try/except is a good solution, other one is `kwargs.get('instance')`. I edited my answer to use `get()`. – Mikko Aug 20 '12 at 20:33
0

I got here because I had a model with a manytomanyfield pointing to this model "CampaignsProducts" which its str property was generating many queries :

Before :

class CampaignsProducts(mdlcom.CommonStructure):
 product = models.ForeignKey(
    'products.Product',
    verbose_name=u'Producto',
    null=False, on_delete=models.CASCADE,
    related_name="%(app_label)s_%(class)s_product_set",
 )
 campaign = models.ForeignKey(
    'prices.Campaigns',
    verbose_name=u'Campaña',
    null=False, on_delete=models.CASCADE,
    related_name="%(app_label)s_%(class)s_campaign_set",
 )
 def __str__(self):
    return '{} - {}'.format(self.product.name, self.campaign.name)

In order to fix it, I changed the following "str" property

def __str__(self):
   return str(self.id)

and the solution that was provided by Mikko, saved my life :

class CouponForm(forms.ModelForm):
NO_REQUIRED = [
    'sellers', 'platforms', 'campaigns_product',
]

class Meta:
    model = mdlpri.Coupon
    fields = '__all__'

def get_campaigns_choices(self):
    qscampaigns = (
        mdlpri.CampaignsProducts.objects.all()
        .select_related("product")
        .select_related("campaign")
        .order_by("id")
    )
    choices = []
    for item in qscampaigns:
        wdescription = '{} - {}'.format(
            item.product.name, item.campaign.name
        )
        choices.append((item.id, wdescription))

    return choices

def __init__(self, *args, **kwargs):
    super(CouponForm, self).__init__(*args, **kwargs)
    for wfield in self.NO_REQUIRED:
        self.fields[wfield].required = False

personalize choices

    w = self.fields["campaigns_product"].widget
    w.choices = self.get_campaigns_choices()

Query Before the change (by product):

SELECT "products_product"."id", "products_product"."name", "products_product"."custom", "products_product"."created_at", "products_product"."updated_at", "products_product"."active", "products_product"."position", "products_product"."url", "products_product"."for_bonus_platform", "products_product"."for_cms_platform", "products_product"."for_store_platform", "products_product"."for_call_center_platform", "products_product"."for_ibk_platform", "products_product"."for_scotiabank_platform", "products_product"."for_bbva_platform", "products_product"."for_refurbished_platform" FROM "products_product" WHERE "products_product"."id" = 1

Query after the change :

SELECT "prices_campaignsproducts"."id", "prices_campaignsproducts"."date_created_at", "prices_campaignsproducts"."created_at", "prices_campaignsproducts"."updated_at", "prices_campaignsproducts"."user_id", "prices_campaignsproducts"."last_user_modified_id", "prices_campaignsproducts"."product_id", "prices_campaignsproducts"."campaign_id", "prices_campaignsproducts"."price", "prices_campaignsproducts"."is_active", "prices_campaignsproducts"."expiration_from", "prices_campaignsproducts"."expiration_to", "products_product"."id", "products_product"."name", "products_product"."custom", "products_product"."created_at", "products_product"."updated_at", "products_product"."active", "products_product"."position", "products_product"."url", "products_product"."for_bonus_platform", "products_product"."for_cms_platform", "products_product"."for_store_platform", "products_product"."for_call_center_platform", "products_product"."for_ibk_platform", "products_product"."for_scotiabank_platform", "products_product"."for_bbva_platform", "products_product"."for_refurbished_platform", "prices_campaigns"."id", "prices_campaigns"."date_created_at", "prices_campaigns"."created_at", "prices_campaigns"."updated_at", "prices_campaigns"."user_id", "prices_campaigns"."last_user_modified_id", "prices_campaigns"."name", "prices_campaigns"."is_active" FROM "prices_campaignsproducts" INNER JOIN "products_product" ON ("prices_campaignsproducts"."product_id" = "products_product"."id") INNER JOIN "prices_campaigns" ON ("prices_campaignsproducts"."campaign_id" = "prices_campaigns"."id") ORDER BY "prices_campaignsproducts"."id" ASC

Just wanted to share my experience.

Best regards,

Manuel Lazo
  • 745
  • 7
  • 7