Django-Tables2 LinkColumn link goes to wrong item - django

I have a Django project and I am having issues with the hyperlink (LinkColumn from Django-tables2) going to the incorrect entry and I cannot figure out why it is occurring or how to fix it.
Very specifically, I can go to the admin view and create a publication. When it comes to setting the author (a.k.a. pi) or sample, there is a drop down menu for foreign key fields (sample/pi) that shows all existing entries from which I can choose one. When I choose a sample and pi then look at the table rendering, the hyperlink is there for the sample, pi, and the publication title. The publication title correctly takes me to the publication_detail page. The hyperlink for the sample will take me to a sample detail page, but it is not the same sample I selected from the admin page. I have the same issue for the author; it takes me to the detail view page of AN author, just not the one I selected from the admin page.
I use django-tables2 several times throughout the project and like how the tables are rendered, but cannot figure out how to address this problem. I have included some of my code (please note I included a bit of the PI and Sample model, but not all of it).
Any assistance is greatly appreciated.
models.py
class PI(models.Model): #this is a smattering of the PI model
l_name = models.CharField('L Name', blank=False, max_length=100, default='')
f_name = models.CharField('F Name', blank=False, max_length=100, default='')
m_name = models.CharField('MI', null=True, blank=True, max_length=1, default='' )
phone = PhoneField(blank=True, default='')
email = models.EmailField('Email', blank=True, max_length=100, default='')
class Sample(models.Model): #this is a smattering of the Sample model
sample_name = models.CharField('Sample', max_length=16)
pi = models.ForeignKey(PI, on_delete=models.SET_NULL, null=True)
submitter = models.ForeignKey('Submitter', blank=True, on_delete=models.SET_NULL, null=True)
class Publication(models.Model):
sample = models.ForeignKey(Sample, on_delete=models.SET_NULL, null=True)
author = models.ForeignKey(PI, on_delete=models.SET_NULL, null=True)
title_p = models.CharField('Title', max_length=200, blank=False, default='')
volume = models.IntegerField('Volume', blank=True, null=True)
number = models.IntegerField('Number', blank=True, null=True)
pages = models.CharField('Pages', default='', max_length=20, blank=True)
year = models.IntegerField('Year', blank=True, null=True)
doi = models.CharField('DOI', default='', max_length=30, blank=False)
journal = models.CharField('Journal', default='', max_length=100, blank=False)
abstract = models.CharField('Abstract', default='', max_length=1000, blank=False)
issn = models.CharField('ISSN', default='', max_length=10, blank=False)
url = models.CharField('URL', default='', max_length=100, blank=False)
eprint = models.CharField('Eprint', default='', max_length=100, blank=False)
class Meta:
ordering = ('sample', 'author', 'title_p', 'journal', 'volume', 'number', 'pages', 'year', 'doi', 'abstract', 'issn', 'url', 'eprint')
def get_absolute_url(self):
return reverse('publication-detail', args=[str(self.id)])
def __str__(self):
return f'{self.sample}, {self.author}, {self.title_p}, {self.volume}, {self.number}, {self.pages}, {self.year}, {self.doi}, {self.journal}, {self.abstract}, {self.issn}, {self.url}, {self.eprint}'
tables.py
class PublicationTable(tables.Table):
sample = tables.LinkColumn('sample-detail', args=[A('pk')])
author = tables.LinkColumn('pi-detail', args=[A('pk')])
title_p = tables.LinkColumn('publication-detail', args=[A('pk')])
class Meta:
model = Publication
fields = ( 'sample', 'author', 'title_p', 'journal', 'year', )
exclude = ( 'volume', 'number', 'pages', 'doi', 'abstract', 'issn', 'url', 'eprint', )
list_display = ('sample', 'author', 'title_p', 'year', 'journal', )
views.py
class PublicationListView(generic.ListView):
model = Publication
paginate_by = 100
#login_required
def publication_view(request, pk):
publication = Publication.objects.get(pk = pk)
table = PublicationTable(Publication.objects.filter(publication=pk))
RequestConfig(request).configure(table)
return render(request, 'samples/publication_detail.html', {'publication': publication, 'publication-detail': table})
#login_required
def publication_table(request):
table = PublicationTable(Publication.objects.all())
RequestConfig(request).configure(table)
return render(request, 'samples/publication_list.html', {'publication_table': table})
class PublicationDetailView(generic.DetailView):
model = Publication
urls.py
urlpatterns = [
path('', views.index, name='index'),
path('samples/', views.sam, name='sam'),
path('sample/<int:pk>', views.SampleDetailView.as_view(), name='sample-detail'),
path('pi/', views.pi_table, name='pi_table'),
path('pi/<int:pk>', views.pi_view, name='pi-detail'),
path('publication/', views.publication_table, name='publication_table'),
path('publication/<int:pk>', views.PublicationDetailView.as_view(), name='publication-detail'),
]
A bit of code from samples/templates/samples/publication_list.py
{% render_table publication_table %}

Well, passing pk through accessor means, it will pass the primary key of Publication Model objects for pi-details, sample-details, and so on. So you need to change it so that you pass the respective primary keys throught the accessor like this:
class PublicationTable(tables.Table):
sample = tables.LinkColumn('sample-detail', args=[A('sample_id')])
author = tables.LinkColumn('pi-detail', args=[A('author_id')])
title_p = tables.LinkColumn('publication-detail', args=[A('pk')])

Related

How do I show only a subset of options in a Django dropdown menu

I have an app that allows users to signup and register for courses (from a 'TrainingInstance' model). These events have names etc and are categorised as Past or Current in the database (in the 'Training' model). When I show the BuildOrderForm in my template, I want only options for Current trainings to be shown in the dropdown menu. How can this be done in Django without javascript or Ajax?
I have the following form in forms.py:
class BuildOrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ['training_registered']
And the following models in models.py:
class Training(models.Model):
""" Model which specifies the training category (name) and whether they are Past or Present"""
YEAR = (
('current', 'current'),
('past', 'past'),
)
name = models.CharField(max_length=200, null=True)
year= models.CharField(max_length=200, null=True, choices=YEAR, default='current')
def __str__(self):
return self.name
class TrainingInstance(models.Model):
""" Creates a model of different instances of each training ( May 2021 etc) """
name = models.CharField(max_length=200, null=True, blank=True)
venue = models.CharField(max_length=200, null=True, blank=True)
training = models.ForeignKey(Training, on_delete= models.CASCADE, null = True)
training_month = models.CharField(max_length=200, null=True, blank=True)
participant_date = models.CharField(max_length=20, null=True, blank=True)
staff_date = models.CharField(max_length=20, null=True, blank=True)
graduation_date = models.CharField(max_length=200, null=True, blank=True)
def __str__(self):
return self.name
class Order(models.Model):
REGSTATUS = (
('registered', 'registered'),
('enrolled', 'enrolled'),
('holding', 'holding'),
('withdrawn', 'withdrawn'),
('waiting', 'waiting'),
)
customer = models.ForeignKey(Customer, on_delete= models.CASCADE, null = True)
training_registered = models.ForeignKey(TrainingInstance, on_delete= models.SET_NULL, blank = True, null = True)
registration_date = models.DateTimeField(null=True,blank=True)
regstatus = models.CharField(max_length=200, null=True, choices=REGSTATUS, default='registered')
def __str__(self):
return self.customer.username
Here is what I have done - which works but I'm also open to feedback about good/bad practice.
class BuildOrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ['training_registered']
def __init__(self,*args,**kwargs):
super (BuildOrderForm,self ).__init__(*args,**kwargs)
self.fields['training_registered'].queryset = TrainingInstance.objects.filter(training__year ="current")

Django admin page autocomplete on reverse ForeignKey

Disclamer:
This is an edit question because I got some answers until now.
I would like to create an autocomplete field in InlineModelAdmin, with similar behavior as autocomplete_fields. I know, Django natively supports this only on ForeignKey, but I need it on the reverse side.
Is there a way - or a library that could do this?
I look into :
https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html
https://django-grappelli.readthedocs.io/en/latest/customization.html
https://django-admin-autocomplete-all.readthedocs.io/en/latest/readme.html
But I didn't found this functionality...
Past question code:
I have models like this:
Models
class Customer(models.Model):
customer_ref = models.CharField(unique=True, max_length=50)
name = models.CharField(null=False, max_length=80)
country = models.CharField(null=True, max_length=50)
class Assortment(models.Model):
name = models.CharField(null=False, max_length=50)
customers = models.ManyToManyField(Customer, related_name='assortments', blank=True)
products = models.ManyToManyField(Product, related_name='assortments', blank=True)
class Subsidiary(models.Model):
subsidiary_ref = models.CharField(unique=True, max_length=50)
name = models.CharField(null=False, max_length=80)
address = models.TextField(null=True)
city = models.CharField(null=True, max_length=50)
coordinates_x = models.DecimalField(null=True, decimal_places=2, max_digits=6)
coordinates_y = models.DecimalField(null=True, decimal_places=2, max_digits=6)
phone_number = models.CharField(null=True, max_length=50)
frequency = models.ForeignKey(Frequency, on_delete=models.SET_NULL, null=True)
channel = models.CharField(null=True, blank=True, max_length=50)
subchannel = models.CharField(null=True, blank=True, max_length=50)
user = models.ForeignKey(User, related_name='subsidiaries', on_delete=models.SET_NULL, null=True)
day_planned = models.BooleanField(default=False)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='subsidiaries')
class Asset(models.Model):
identification = models.CharField(unique=True, max_length=50)
serial = models.CharField(max_length=50)
name = models.CharField(null=True, max_length=50)
subsidiary = models.ForeignKey(Subsidiary, related_name='assets', null=True, blank=True, on_delete=models.DO_NOTHING)
Admin
#admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ['customer_ref', 'name', 'country']
list_filter = ['country']
autocomplete_fields = ['assortments']
#admin.register(Subsidiary)
class SubsidiaryAdmin(admin.ModelAdmin):
exclude = ['day_planned']
list_display = ['subsidiary_ref', 'customer', 'name', 'address', 'phone_number', 'frequency', 'user']
list_editable = ['frequency', 'user']
list_filter = ['frequency', 'user']
search_fields = ['subsidiary_ref', 'customer__name', 'name']
autocomplete_fields = ['assets']
CustomerAdmin is 'working' without error but field 'assortments' is not visible.
SubsidiaryAdmin throws error :
<class 'mobileapp.admin.SubsidiaryAdmin'>: (admin.E038) The value of 'autocomplete_fields[0]' must be a foreign key or a many-to-many field.
witch is weird because I don't see any difference from the first example.
How to fix this?
assortments is not visible in the list page as you have set list_display to
list_display = ['customer_ref', 'name', 'country']
Since assortments is a many-to-many relationship, you have to write a custom ModelAdmin to display them.
As mentioned in the django docs, autocomplete_fields works only for FK & M2M fields of that model. In the AssetAdmin, you can set autocomplete for Subsidiary field.
#admin.register(Asset)
class AssetAdmin(admin.ModelAdmin):
autocomplete_fields = ['subsidiary']

Django. How to write a filter for the current user?

The listings application has a Listing table:
class Listing(models.Model):
realtor = models.ForeignKey(Realtor, on_delete=models.CASCADE, verbose_name='Риэлтор')
region = models.CharField(default="Чуйская", max_length=100, verbose_name='Область')
city = models.CharField(default="Бишкек", max_length=100, verbose_name='Город')
district = models.CharField(blank=True, max_length=100, verbose_name='Район')
title = models.CharField(max_length=200, verbose_name='Заголовок')
address = models.CharField(blank=True, max_length=200, verbose_name='Адрес')
description = models.TextField(blank=True, verbose_name='Описание')
stage = models.IntegerField(blank=True, verbose_name='Этажность')
rooms = models.IntegerField(blank=True, verbose_name='Количество комнат')
garage = models.IntegerField(default=0, blank=True, verbose_name='Гараж')
sqmt = models.IntegerField(blank=True, verbose_name='Площадь')
price = models.IntegerField(blank=True, verbose_name='Цена')
photo_main = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Основное фото')
photo_1 = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Фото 1')
photo_2 = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Фото 2')
photo_3 = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Фото 3')
photo_4 = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Фото 4')
photo_5 = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Фото 5')
photo_6 = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True, verbose_name='Фото 6')
is_published = models.BooleanField(default=True, verbose_name='Публично')
list_date = models.DateTimeField(default=datetime.now, blank=True, verbose_name='Дата публикации')
def __str__(self):
return self.title
class Meta:
verbose_name = 'Объявление'
verbose_name_plural = 'Объявления'
In the realtors application there is a Realtor model:
class Realtor(models.Model):
user_name = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='Пользователь', related_name='realtor')
name = models.CharField(max_length=20, verbose_name='Имя')
photo = models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Фото')
description = models.TextField(blank=True, verbose_name='Описание')
phone = models.CharField(max_length=20, verbose_name='Телефон')
email = models.CharField(max_length=50, verbose_name='Email')
is_mvp = models.BooleanField(default=False, verbose_name='Реэлтор месяца')
hire_date = models.DateTimeField(default=datetime.now, blank=True, verbose_name='Дата приёма на работу')
def __str__(self):
return self.name
class Meta:
verbose_name = 'Риэлтор'
verbose_name_plural = 'Риэлторы'
In the accounts application, there is a function that in the personal account should only display ads of the current user when he is in the system:
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from listings.models import Listing
from realtors.models import Realtor
def dashboard(request):
listings = Listing.objects.order_by('-list_date').filter(user_name=request.user)
paginator = Paginator(listings, 6)
page = request.GET.get('page')
paged_listings = paginator.get_page(page)
context = {
'listings': paged_listings
}
return render(request, 'accounts/dashboard.html', context
)
How to correctly register this filter so that everything works so that the current user’s ads are displayed:
listings = Listing.objects.order_by('-list_date').filter(user_name=request.user)
At the moment, this error:
Cannot resolve keyword 'user_name' into field. Choices are: address, city, description, district, garage, id, is_published, list_date, photo_1, photo_2, photo_3, photo_4, photo_5, photo_6, photo_main, price, realtor, realtor_id, region, rooms, sqmt, stage, title
Who is not difficult, please help. Thank you in advance.
Since there's no user_name field in Listing, it's an error to try and filter on that.
Instead, you presumably are trying to filter on the realtor, which can done with a lookup that spans relationships:
listings = Listing.objects.order_by('-list_date').filter(realtor__user_name=request.user)
user_name is a field on the Realtor model, not the Listing. Those two models are connected by a ForeignKey, so you need to traverse that relationship using the double-underscore syntax.
Listing.objects.order_by('-list_date').filter(realtor__user_name=request.user)
Note though that user_name is a very odd name for that field; it's not the name, it's the User object itself. It should be called just user.

django admin and inlines: many-to-many with 'through' model - performance issue

I have a problem with a m2m field that use an intermediated model ('through') with the admin and inlines. here's the code:
# MODELS
class Engagement(models.Model):
parent_engagement = models.ForeignKey('self', blank=True, null=True, related_name='child_engagements')
title = models.CharField('Engagement title', max_length=200)
...
# client
client = models.ForeignKey(Client, related_name='engagements')
primary_point_of_contact = models.ForeignKey(
ClientContact, null=True, blank=True,
related_name='engagements_for_which_point_of_contact'
)
additional_point_of_contacts = models.ManyToManyField(
ClientContact,
through='AdditionalPointOfContact'
)
.... # other fields
class ClientContact(models.Model):
first_name = models.CharField(max_length=200, blank=True)
last_name = models.CharField(max_length=200, blank=True)
jobtitle = models.CharField(max_length=200, blank=True)
company = models.ForeignKey(Client, null=True, blank=True)
class AdditionalPointOfContact(models.Model):
engagement = models.ForeignKey("Engagement", related_name='additional_points_of_contact')
client_contact = models.ForeignKey("ClientContact")
description = models.CharField(max_length=500, blank=True)
def __unicode__(self):
return self.client_contact.__unicode__()
# ADMIN
class EngagementAdmin(ChaosDefaultAdmin):
....
inlines = [
ScopeServiceElementAdmin,
AdditionalPointOfContactInlineAdmin
]
list_display = (...
class AdditionalPointOfContactInlineAdmin(admin.TabularInline):
model = AdditionalPointOfContact
fieldsets = [
('', {
'fields': (('client_contact',),
('description',),)
}),
]
extra = 0
min_num = 0
according to the django-debug-toolbar, the sql tab says 8382 queries, while commenting out AdditionalPointOfContactInlineAdmin it reduces to 10 queries, so there's something wrong there.
I might override the get_queryset method of AdditionalPointOfContactInlineAdmin, but I don't know exactly how and why. Any suggestion?
Try override EngagementAdmin.get_queryset():
class EngagementAdmin(ChaosDefaultAdmin):
def get_queryset(self, request):
qs = super(EngagementAdmin, self).get_queryset(request)
return qs.prefetch_related('additional_points_of_contact')
# or
# return qs.prefetch_related('additional_points_of_contact__client_contact')

Django: creating M2M relationship inline

I'm trying to create a view with a formset of forms for the "Link" model. The problem is that in each form I would like the user to have the possibility of not just choosing from the already created TargetLink objects, but to edit them inline.
class ClientUrl(models.Model):
client = models.ForeignKey(UpstreamClientModel, null=True)
url = models.URLField(unique=True, null=False)
active = models.BooleanField(default=True)
def __unicode__(self):
return self.url
class Meta:
verbose_name = 'url'
ordering = ['url']
KEYWORD_TYPES = (
('PN', 'Pending'),
('MN', 'Money'),
('BR', 'Brand'),
('LT', 'Long Tail'),
)
class ClientKeyword(models.Model):
client = models.ForeignKey(UpstreamClientModel, null=True)
kw_type = models.CharField('keyword type', max_length=2,
choices=KEYWORD_TYPES, default='LT')
keyword = models.CharField(max_length=150, unique=False)
directions = models.CharField(max_length=200, blank=True,
help_text='e.g: 25 chars, use "affordable rental cars"')
def __unicode__(self):
return self.keyword
class Meta:
ordering = ['keyword', ]
unique_together = ('client', 'keyword')
class TargetLink(models.Model):
keyword = models.ForeignKey(ClientKeyword, related_name='target_links')
url = models.ForeignKey(ClientUrl, related_name='target_links')
def __unicode__(self):
return '{0}:{1}'.format(self.keyword, self.url)
class Link(models.Model):
client = models.ForeignKey(UpstreamClientModel, related_name='links')
target = models.ForeignKey(Target, related_name='links')
user = models.ForeignKey(User, blank=True, null=True, related_name='links')
link_type = models.ForeignKey(LinkType, related_name='links')
site = models.ForeignKey(Site, blank=True, null=True,
related_name='links')
site_url = models.URLField(blank=True,
help_text='leave blank until url is live')
month = models.DateField(blank=True, null=True)
target_links = models.ManyToManyField(TargetLink, related_name='links')
How could I accomplish this?
One way might be to have a form that is outside of your formset for the TargetLinks and use Knockout.js or another client-side framework to push the updated choices for TargetLinks to the target_links field in the formsets.