Forwading ManyToMany model field in django-autocomplete-light - django

models.py
class InvestmentGroup(models.Model):
name = models.CharField(max_length=200, blank=True, null=True)
persons = models.ManyToManyField('Person', blank=True, related_name='investment_groups')
lead_investor = models.ForeignKey(
'Person', blank=True, null=True, on_delete=models.CASCADE, related_name='lead_investment_groups'
)
forms.py
class InvestmentGroupModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InvestmentGroupModelForm, self).__init__(*args, **kwargs)
class Meta:
model = models.InvestmentGroup
fields = '__all__'
widgets = {
"lead_investor": autocomplete.ModelSelect2(
url="lead-investor-autocomplete",
forward=["persons"]
)
}
AutoCompleteview
class LeadInvestorAutoComplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated:
return models.Person.objects.none()
qs = models.Person.objects.all()
persons = self.forwarded.get('persons', None)
print(persons) # Output: []
if persons:
qs = qs.filter(person__in=persons)
return qs
I am getting empty values if I forward many to many field like this but works for other fields like name or foreign keys.
Is it possible to forward ManyToMany field?

You're not forwarding the persons field, which is why it seems to be empty.
If you change your form to this, it should work:
class InvestmentGroupModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(InvestmentGroupModelForm, self).__init__(*args, **kwargs)
class Meta:
model = models.InvestmentGroup
fields = '__all__'
widgets = {
"lead_investor": autocomplete.ModelSelect2(
url="lead-investor-autocomplete",
forward=["persons"]
)
}

Related

Django Rest Framework nested serializer gives attribute error

I am trying to setup a nested serializer below I've posted the models and serializer. why am I getting the below error ?
Got AttributeError when attempting to get a value for field `balance_sheet` on serializer `StockSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Stock` instance.
Original exception text was: 'Stock' object has no attribute 'balance_sheet'.
serializers.py
class StockSerializer(serializers.ModelSerializer):
income_statement = IncomeStatementSerializer(many=True)
balance_sheet = BalanceSheetSerializer(many=True)
cashflows_statement = CashflowsStatementSerializer(many=True)
def create(self, validated_data):
temp_income_statement_data = validated_data.pop("income_statement")
temp_balance_sheet_data = validated_data.pop("balance_sheet")
temp_cashflows_statement_data = validated_data.pop("cashflows_statement")
new_stock = Stock.objects.create(**validated_data)
for i in temp_income_statement_data:
IncomeStatement.objects.create(**i, ticker=new_stock)
for x in temp_balance_sheet_data:
BalanceSheet.objects.create(**x, ticker=new_stock)
for y in temp_cashflows_statement_data:
CashflowsStatement.objects.create(**y, ticker=new_stock)
return new_stock
IncomeStatementSerializer, BalanceSheetSerializer and CashflowsStatementSerializer are all typical ModelSerializer's
models.py
class Stock(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
ticker = models.CharField(max_length=10, unique=True, primary_key=True)
slug = models.SlugField(default="", editable=False)
def save(self, *args, **kwargs):
value = self.ticker
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
def __str__(self):
return self.ticker
class Meta:
verbose_name = "stock"
verbose_name_plural = "stocks"
ordering = ["ticker"]
IncomeStatement, BalanceSheet and CashflowsStatement are all typical models.Model with a ForeignKey relationship to Stock
views.py
class StockList(generics.ListCreateAPIView):
queryset = Stock.objects.all()
serializer_class = StockSerializer
lookup_field = "slug"

DRF: How to insert data in another model class on deleting on model class

I am trying to insert data on one modal class on destroying another model class. My First model class:
class UserDevice(models.Model):
type = models.ForeignKey(
'Device',
on_delete=models.CASCADE
)
hardware_serial = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
class NewDevice(models.Model):
type = models.ForeignKey(
'Device',
on_delete = models.CASCADE
)
hardware_serial = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
Whenever I will destroy the UserDevice I want to the same device should be added in NewDevice
My serializer class is :
class UserDeviceSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserSensorDevice
fields = '__all__'
read_only_fields = ['id','created_at']
def create(self, validated_data):
app_id = models.UserDevice.objects.filter(sensor_code= validated_data.get('hardware_serial', None)).first()
if app_id:
msg = _('Application ID is already registered')
raise serializers.ValidationError(msg, code='authorization')
else :
return models.UserDevice.objects.create(**validated_data)
class NewDeviceSerializer(serializers.ModelSerializer):
class Meta:
model = models.NewDevice
fields = '__all__'
read_only_fields = ['id','created_at']
My view class:
class UserSensorRemoveDevice(generics.DestroyAPIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
queryset = models.UserDevice.objects.all()
serializer_class = serializers.UserDeviceSerializer
What I have tried to override UserDevice destroy. But somehow its not working:
def destroy(self, validated_data):
return models.NewDevice.objects.create(type = validated_data.get('type', None),hardware_serial = validated_data.get('hardware_serial', None))
Any help will be highly appreciated. Thanks in advance.
You can override UserDevice.delete() method
class UserDevice(models.Model):
...
def delete(self, *args, **kwargs):
NewDevice.objects.create(type=self.type, hardware_serial=self.hardware_serial)
super().delete(*args, **kwargs)
in can just override your model UserDevice delete function
def delete(self, using=None, keep_parents=False):
#add new NewDevice instance
super().delete(using, keep_parents,*args,**kwargs)

How to filter or query using abstract parent class in Django model?

This one is interesting to solve. I am building a module to register address for hospital, medical store and doctors. There is an abstracted model PrimaryAddress and a subclass called MedicalStorePrimaryAddress, and more subclasses will use the same abstracted model. I am using django rest framework to get the listings based on proximity (latitude, longitude and city). Now how could I filter it all using parent class, i.e PrimaryAddress model as I want to filter all the entities, i.e hospital, medical store and doctor nearby.
I have looked into django-polymorphic library but it doesnt help with geodjango and abstract class.
Any help suggestion is appreciated. Thanks
Here is the code sample:
# MODELS
class PrimaryAddress(gismodels.Model):
street = gismodels.CharField(max_length=255)
city = gismodels.CharField(max_length=60)
state = gismodels.CharField(max_length=100,
choices=settings.US_STATES,
default="CT")
landmark = gismodels.TextField()
latitude = gismodels.FloatField(null=True, blank=True)
longitude = gismodels.FloatField(null=True, blank=True)
location = gismodels.PointField(null=True, blank=True)
objects = gismodels.GeoManager()
def __unicode__(self):
return self.street
class Meta:
verbose_name = "Address"
verbose_name_plural = "Addresses"
abstract = True
def save(self, *args, **kwargs):
if self.latitude and self.longitude:
self.location = Point(self.longitude, self.latitude)
super(PrimaryAddress, self).save(*args, **kwargs)
class MedicalStoreAddress(PrimaryAddress):
medical_store = gismodels.OneToOneField(MedicalStore, related_name="medical_store_address",
on_delete=gismodels.CASCADE, null=True, blank=True)
# objects = gismodels.GeoManager()
def __unicode__(self):
return self.street
class Meta:
verbose_name = "Medical Store Address"
verbose_name_plural = "Medical Store Addresses"
def save(self, *args, **kwargs):
if self.latitude and self.longitude:
self.location = Point(self.longitude, self.latitude)
super(MedicalStoreAddress, self).save(*args, **kwargs)
# VIEW
class ProximityFilter(ListAPIView):
serializer_class = AddressSerializer
# authentication_classes = (authentication.TokenAuthentication, authentication.SessionAuthentication,)
# permission_classes = (permissions.IsAuthenticated,)
pagination_class = StandardResultsSetPagination
def get_queryset(self):
longitude = self.kwargs.get('longitude')
latitude = self.kwargs.get('latitude')
city = self.kwargs.get('city')
current_point = GEOSGeometry('POINT(%s %s)' % (longitude, latitude), srid=4326)
# raise
queryset = MedicalStoreAddress.objects.filter(city__iexact=city, location__distance_lte=(current_point, D(mi=700000000))).distance(
current_point).order_by('distance')
return queryset
# SERIALIZER
class AddressSerializer(HyperlinkedModelSerializer):
class Meta:
model = DoctorPrimaryAddress
fields = ('pk', 'street', 'latitude', 'longitude', 'city')
This paste expires on 2018-03-29 21:26:23. View raw. Remove now (Why am I seeing this?) Pasted through web.

Add some parameters to FilterSet (django-filter) + some parameters

i have 3 models:
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=70, null=True, blank=True)
class SubCategory(models.Model):
category= models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=200, )
class Products(models.Model):
user= models.ForeignKey(User, on_delete=models.CASCADE)
category= models.ForeignKey(Category, on_delete=models.CASCADE)
subcategory = models.CharField(max_length=200, null=True, blank=True)
and i have a view which receive request and category.slug
def category_list(request, slug):
category = Category.objects.get(slug=slug)
products = ProductFilter(request.GET, queryset=Products.objects.filter(category=category)
return render(request, 'products/category_list.html', {"products":products, 'category': category})
when rendering i receive a QuerySet filtered to Category
I want to send category.id to ProductsFilter and recive a dynamic Choices from database
class ProductsFilter(django_filters.FilterSet):
subcategory= django_filters.ChoiceFilter(lookup_expr='iexact', choices=TEST, required=False)
class Meta:
model = Products
fields = {
"subcategory",
}
Want to change choices=TEST to choices=list(SubCategory.objects.filter(category_id=category.id)
Is this possible?
The answer from #Sherpa has just two slight problems. First, you should replace fields with filters. Second, you can't use += operator, you have to directly assign to the filter's extra.
Here's my working code in two different ways
class LayoutFilterView(filters.FilterSet):
supplier = filters.ChoiceFilter(
label=_('Supplier'), empty_label=_("All Suppliers"),)
def __init__(self, *args, **kwargs):
super(LayoutFilterView, self).__init__(*args, **kwargs)
# First Method
self.filters['supplier'].extra['choices'] = [
(supplier.id, supplier.name) for supplier in ourSuppliers(request=self.request)
]
# Second Method
self.filters['supplier'].extra.update({
'choices': [(supplier.id, supplier.name) for supplier in ourSuppliers(request=self.request)]
})
Originally posted here
You can handle this in the FilterSet.__init__ method. Something like the below (Note that I haven't tested it, may require some fiddling):
class ProductsFilter(django_filters.FilterSet):
subcategory= django_filters.ChoiceFilter(lookup_expr='iexact', choices=[], required=False)
def __init__(self, category, *args, **kwargs):
super(ProductsFilter, self).__init__(*args, **kwargs)
choices = self.fields['subcategory'].extra['choices']
choices += [
(subcat.name, subcat.name) for subcat
in SubCategory.objects.filter(category=category)
]
class Meta:
model = Products

'form.is_valid()' fails when I want to save form with choices based on two models

I want to let users to choose their countries. I have 2 models = Countries with some figures and CountriesTranslations. I am trying to make tuple with country (because user has FK to this model) and its translation. In front-end I see dropdown list of countries, but when I try to save the form, I see
error: Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance.
Error happens at the line if user_profile_form.is_valid():
# admindivisions.models
class Countries(models.Model):
osm_id = models.IntegerField(db_index=True, null=True)
status = models.IntegerField()
population = models.IntegerField(null=True)
iso3166_1 = models.CharField(max_length=2, blank=True)
iso3166_1_a2 = models.CharField(max_length=2, blank=True)
iso3166_1_a3 = models.CharField(max_length=3, blank=True)
class Meta:
db_table = 'admindivisions_countries'
verbose_name = 'Country'
verbose_name_plural = 'Countries'
class CountriesTranslations(models.Model):
common_name = models.CharField(max_length=81, blank=True, db_index=True)
formal_name = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Countries, on_delete=models.CASCADE, verbose_name='Details of Country')
lang_group = models.ForeignKey(LanguagesGroups, on_delete=models.CASCADE, verbose_name='Language of Country',
null=True)
class Meta:
db_table = 'admindivisions_countries_translations'
verbose_name = 'Country Translation'
verbose_name_plural = 'Countries Translations'
# profiles.forms
class UserProfileForm(forms.ModelForm):
# PREPARE CHOICES
country_choices = ()
lang_group = Languages.objects.get(iso_code='en').group
for country in Countries.objects.filter(status=1):
eng_name = country.countriestranslations_set.filter(lang_group=lang_group).first()
if eng_name:
country_choices += ((country, eng_name.common_name),)
country_choices = sorted(country_choices, key=lambda tup: tup[1])
country = forms.ChoiceField(choices=country_choices, required=False)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
# profiles.views
def profile_settings(request):
if request.method == 'POST':
user_profile_form = UserProfileForm(request.POST, instance=request.user)
if user_profile_form.is_valid():
user_profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_profile_form = UserProfileForm(instance=request.user)
return render(request, 'profiles/profiles_settings.html', {
'user_profile_form': user_profile_form,
})
As I understand, country from ((country, eng_name.common_name),) is converted to str. What is the right way to keep country instance in the form? or if I am doing it in the wrong way, what way is correct?
EDITED:
As a possible solution is to use ModelChoiceField with overriding label_from_instance as shown below:
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang='en', *args, **kwargs):
super(CountriesChoiceField, self).__init__(*args, **kwargs)
self.user_lang = user_lang
def label_from_instance(self, obj):
return obj.countriestranslations_set.get(lang_group=self.user_lang)
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(
queryset=Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name'),
widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
but this solution produces too much queries because of the query in label_from_instance and page loads too slowly. Would appreciate any advice.
You probably want to use forms.ModelChoiceField instead of the forms.ChoiceField for your dropdown list.
The ModelChoiceField builds based on a QuerySet and preserves the model instance.
Seems to be solved.
The version below produces 7 queries in 29.45ms vs 73 queries in 92.52ms in the EDITED above. I think it is possible to make it even faster if to set unique_together for some fields.
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang, *args, **kwargs):
queryset = Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name')
super(CountriesChoiceField, self).__init__(queryset, *args, **kwargs)
self.translations = OrderedDict()
for country in queryset:
name = country.countriestranslations_set.get(lang_group=user_lang).common_name
self.translations[country] = name
def label_from_instance(self, obj):
return self.translations[obj]
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
So, now it is possible to have choices based on two (2) models with a good speed. Also the DRY principle is applied, so if there is a need to use choices multiple times in different forms - no problem.