Saving centroid of a (multi)polygon as point geometry in a model - python

I have two tables, one with multipolygon geometries, the other with a column for point geometries. I want the centroid of the selected polygon to be saved as the point geometry for the other table.
class matview_all_administrative_units(models.Model):
lau_id = models.IntegerField(primary_key=True)
ortsgemeinde = models.CharField(max_length=150)
verwaltungsgemeinde = models.CharField(max_length=150)
landkreis_bezirk = models.CharField(max_length=150)
bundesland_kanton = models.CharField(max_length=150)
staat = models.CharField(max_length=150)
geom = models.MultiPolygonField(srid=4326)
class Meta:
managed = False
db_table = 'administrative_hierarchy_full_geom'
class site(models.Model):
sid = models.AutoField(primary_key=True)
site_name = models.CharField(max_length=250)
site_notes = models.CharField(max_length=2500, blank=True, null=True)
municipality = models.ForeignKey('local_administrative_unit', on_delete=models.PROTECT)
geom = models.PointField(srid=4326)
def __str__(self):
return '{}, {} ({})'.format(self.sid, self.site_name, self.municipality)
To add a new site, an existing administrative unit must be associated with it and the center of it's polygon should be used as the location/geometry of the site. For now I made this:
class NewSiteView(CreateView):
model = models.site
form_class = forms.NewSiteForm
template_name = 'datamanager/newsite.html'
success_url = '/sites/'
calling this form:
from django.forms import ModelForm, HiddenInput
from django.contrib.gis.db.models.functions import Centroid
from . import models
class NewSiteForm(ModelForm):
class Meta:
model = models.site
fields = ['site_name', 'site_notes', 'municipality','geom']
widgets = {
'geom': HiddenInput(),
}
def clean(self):
super().clean()
self.cleaned_data['geom'] = Centroid(models.matview_all_administrative_units.objects.values('geom').filter(lau_id=self.cleaned_data['municipality'].lau_id))
however, this leads to this error:
So I am basically not calculating a point but a 'centroid object' - so far so good, the django documentation tells us that. Now I am stuck trying to get something out of this centroid thingy that I can shove into that geometry column. As far as I understand I take the right data and handle it over to the right function (otherwise the error should appear earlier in this code i think?), but the result is not useful for inserting it into a geometry column. So how do i get my point? (lol)

finally I found a solution. Added this to the CreateView:
def form_valid(self, form):
pol = models.local_administrative_unit.objects.values('geom').filter(lau_id=form.cleaned_data['municipality'].lau_id)[0]['geom']
cent_point = pol.centroid
form.instance.geom = cent_point.wkt
form.save()
return super().form_valid(form)
I takes the geometry from the polygon, calculates the centroid of it and inserts it's geometry as well-known text into the form, then saves the form.

Related

Django admin inline conditioned on model field values

I'm trying to show one of three different child model inlines for a parent model, based on a selected field value within the parent model. I have tried every solution that pops up from various google searches but nothing seems to work.
First a little background, I'm new to python and django but so far I feel that I have been a quick study. I'm attempting to build a web application to house information linked to various spatial locations. The geometry type (geom_type) for each location may be different (i.e., points, linestring, and polygons are possible). To capture this information I plan to create a parent model (Location) to house the name and geom_type (and possibly other metadata). The spatial data related to each Location would then be housed in three separate child models; one for each geom_type. When entering data I would like to create a new location and select the geom_type, which would then pull up the correct inline.
Now for the details:
Models
from django.contrib.gis.db import models
class Geometry(models.Model):
TYPE = (
('Point', 'Point'),
('Linestring', 'Linestring'),
('Polygon', 'Polygon'),
)
geom_type = models.CharField('Geometry Type', choices = TYPE, max_length = 30)
class Meta:
verbose_name = 'Geometry'
verbose_name_plural = 'Geometries'
def __str__(self):
return self.geom_type
class Location(models.Model):
name = models.CharField('Location Name', max_length = 50)
geom_type = models.ForeignKey(Geometry, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Point(models.Model):
name = models.OneToOneField(Location, on_delete=models.CASCADE)
geometry = models.PointField()
def __str__(self):
return self.name.name
class Linestring(models.Model):
name = models.OneToOneField(Location, on_delete=models.CASCADE)
geometry = models.LineStringField()
def __str__(self):
return self.name.name
class Polygon(models.Model):
name = models.OneToOneField(Location, on_delete=models.CASCADE)
geometry = models.PolygonField()
def __str__(self):
return self.name.name
Admin
from django.contrib.gis import admin
from leaflet.admin import LeafletGeoAdmin, LeafletGeoAdminMixin
from .models import Geometry, Location, Point, Linestring, Polygon
class GeometryAdmin(admin.ModelAdmin):
list_display = ('id', 'geom_type')
admin.site.register(Geometry, GeometryAdmin)
class PointInline(LeafletGeoAdminMixin, admin.StackedInline):
model = Point
class LinestringInline(LeafletGeoAdminMixin, admin.StackedInline):
model = Linestring
class PolygonInline(LeafletGeoAdminMixin, admin.StackedInline):
model = Polygon
class LocationAdmin(admin.ModelAdmin):
model = Location
list_display = ('id', 'name', 'geom_type')
inlines = [
PointInline,
LinestringInline,
PolygonInline
]
admin.site.register(Location, LocationAdmin)
All three inlines show up correctly with the code above as expected. However, when I try to incorporate the conditional logic with different variations of get_inlines or get_inline_instances it always just ends up displaying the inline associated with the final "else" statement.
My failed attempt
def get_inlines(self, request, obj: Location):
if obj.geom_type == 'Point':
return [PointInline]
elif obj.geom_type == 'Location':
return [LinestringInline]
elif obj.geom_type == 'Polygon':
return [PolygonInline]
else:
return []
I believe the problem occurs because conditional statements are not referencing the model field correctly. But I can't seem to stumble upon the correct way to achieve my expected outcome.
Use related_name in model like below:
next_question = models.ForeignKey(Question, on_delete=models.CASCADE, null = True, blank = True, related_name='next_question', limit_choices_to={'is_active': True})
and then fk_name Like the example below: Then try. Hope you can find a solution by yourself.
class Labels(admin.TabularInline):
model = Label
extra = 0
fk_name = "next_question"
Use admin.StackedInline for OneToOne and admin.TabularInline for ForeignKey.
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
Create separate admin for 'Geometry' and 'Location' if you stacked.

i need to convert location coordinates to distance and then filter the queryset accordingly using django-filter

Following is relevant code:
Models.py
class RentTypeChoices(models.IntegerChoices):
DAILY = 0
HOURLY = 1
MONTHLY = 2
rent_type = models.PositiveSmallIntegerField(
choices=RentTypeChoices.choices)
title = models.CharField(max_length=256)
available_from = models.DateField(default=datetime.date.today)
category = models.ForeignKey(
Category, related_name="rent_posts", on_delete=models.CASCADE)
price = models.DecimalField(decimal_places=2, max_digits=6)
# Need to convert below location coords to distance
latitude = models.CharField(max_length=120)
longitude = models.CharField(max_length=120)
is_available = models.BooleanField(default=True)
Filters.py
from django_filters import rest_framework as filters
from apps.embla_services.models import RentPost
class RentPostFilters(filters.FilterSet):
title = filters.CharFilter(lookup_expr="icontains")
is_available = filters.BooleanFilter()
rent_type = filters.ChoiceFilter(choices=RentPost.RentTypeChoices.choices)
category__id = filters.NumberFilter()
available_from = filters.DateFromToRangeFilter()
price = filters.RangeFilter()
# i need to filter this model by distance also
# search query would contain the min or the maximum distance and i would need to filter
# accordingly
class Meta:
model = RentPost
fields = ["title", "is_available",
"rent_type", "category", "available_from", "price"]
Views.py
from django_filters import rest_framework as filters
from apps.embla_services.models import RentPost
from apps.embla_services.filters import RentPostFilters
class SearchRentPosts(generics.ListAPIView):
queryset = RentPost.objects.all()
serializer_class = RentPostBriefSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = RentPostFilters
i know how to convert the location coords between 2 points by using this post Getting distance between two points based on latitude/longitude. however how to exactly use this knowledge to filter the location coordinates to get the distance is my problem.
following the example search request from client side
{{baseUrl}}/rent/search-posts?title=k&rent_type=0&category=14&available_from_after=2022-04-`26&available_from_before=2022-10-05&price_min=40.00&price_max=45.00&distance_max = 5&distance_min=1`
everything else is being done perfectly, only problem is distance part. client need to filter the search results by the distance between his current location and location of the rent post. we would be getting location coords of the client, and we would also have the location coords of the rent post saved
Hope i have explained my question well.
Ps. I haven't added the serializer code as it is not required in this post.

conflict with post save and __unicode__(self) in django models

Apologies for the strange title, but caught on a funny problem involving a conflict with post.save (from a form) and the unicode(self) return in my model.
Models.py
class NumericTraits(models.Model):
feature_id = models.IntegerField(primary_key=True)
species = models.ForeignKey('Species')
traits = models.CharField(max_length=30)
cite = models.ForeignKey(Citation)
username = models.CharField(max_length=30)
dt = models.CharField(max_length=30)
def __unicode__(self):
return self.species_id + self.traits + self.cite_id
class Meta:
db_table = 'numeric_traits'
verbose_name = "Numeric Traits"
verbose_name_plural = "Numeric Traits"
class Citation(models.Model):
cite_id = models.CharField(primary_key=True, max_length=25, default=citation_id_create)
citation_name = models.CharField(max_length=100)
citation = models.TextField()
def __unicode__(self):
return self.citation_name
class Meta:
managed = False
db_table = 'citation'
ordering = ['citation_name']
views.py
def dbPost(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save( commit = False)
citeId = request.POST.get("citation", "")
post.cite_id = Citation.objects.get(cite_id = citeId)
post.save()
return render(request, 'app/SaveSuccess.html')
else:
form = PostForm()
In this case, I'm posting a value of (for example) 'citation1' - which refers to the primary key I"m using here. I use "self.citation_name" (which is "Ainley et al 1995) to display an intuitive name in the django admin.
however, when I go to save, this just gives me an error (e.g., cite_id = Ainley et al 1995 does not exist).
So, it's taking the value of self.citation_name and returning it, then attempting to find the cite_id that matches. However, I want it to return the cite_id value, locate the record, while maintaining the self.citation_name in my model so I can read the admin records easier.
Any help is greatly appreciated
Thanks
of course... always something simple.. I guess when I was trying to assign "post.cite_id", it was trying to assign it to the foreign key in a funny way.. Fixed this by changing.
post.cite_id = .....
to
post.cite = .....

Recursive Relationship with a Description in Django Models

My project involves sorting many images. As part of this sorting, I want to be able to manually (as the user) mark several images as duplicates of each other with a brief description of why each relationship was created. These relationships will not be defined at the time an image is loaded into Django, but at a later time after uploading all the images.
My question: How can I create an unlimited number of duplicates? Aka, how would I define that several images are all related to each other, and include a CharField description of why each relationship exists?
This is a django app and the code is from models.py.
Thank you.
from django.db import models
class tag(models.Model):
tag = models.CharField(max_length=60)
x = models.IntegerField(null=True)
y = models.IntegerField(null=True)
point = [x,y]
def __unicode__(self):
return self.tag
#...
class image(models.Model):
image = models.ImageField(upload_to='directory/')
title = models.CharField(max_length=60, blank=True, help_text="Descriptive image title")
tags = models.ManyToManyField(tag, blank=True, help_text="Searchable Keywords")
#...
##### HELP NEEDED HERE ##################
duplicates = [models.ManyToManyField('self', null=True), models.CharField(max_length=60)]
##########################################
def __unicode__(self):
return self.image.name
You'd have to go with an extra model for grouping those duplicates, because you want a description field with it. Something like
class DupeSeries(Model):
description = CharField(...)
members = ManyToManyField("image", related_name="dupes", ...)
Example usage:
img = image(title="foo!", image="/path/to/image.jpg")
dup_of_img = image(title="foo!dup", image="/path/to/dup/image.jpg")
img.save()
dup_of_img.save()
dupes_of_foo = DupeSeries(description="foo! lookalikes")
dupes_of_foo.members.add(img, dup_of_img)
# Notice how *img.dupes.all()* returns both image instances.
assert(list(img.dupes.all()) == [img, dup_of_img])

Django: Custom widget for ForeignKey in geolocation app

I am working on an application that manage the locations for a web site. This application (locations) contains the model(simplified):
class Location(models.Model):
""" Model for storing locations, places and addresses """
address = models.CharField(_('address'), max_length=100, blank=True)
""" Location address """
latitude = models.FloatField(_('latitude'), blank = True)
""" Latitude info point """
longitude = models.FloatField(_('longitude'), blank = True)
""" Longitude info point """
So that other applications and models who want to store a location, actually contain a ForeignKey to this model.
In addition, the locations application, defines a widget that instead of displaying the typical select associated with ForeignKey fields, renders a google map supported by a javascript that saves the latitude, longitude, and address in a HiddenInput field(also simplified):
class LocationEditWidget(HiddenInput):
def __init__(self, attrs=None):
default_attrs = {}
if attrs:
default_attrs.update(attrs)
super(LocationEditWidget, self).__init__(default_attrs)
class Media:
js = (
'http://maps.google.com/maps/api/js?sensor=true',
'js/gmaps.js',
'js/location-select.js',
)
def render(self, name, value, attrs=None):
self.attrs['id'] = self.attrs.get('id', 'unique')
self.attrs['latitude'] = self.attrs.get('latitude', 'auto')
self.attrs['longitude'] = self.attrs.get('longitude', 'auto')
self.attrs['geocoding'] = self.attrs.get('geocoding', 'True')
self.attrs['geolocation'] = self.attrs.get('geolocation', 'True')
self.attrs['width'] = self.attrs.get('width', '400')
self.attrs['height'] = self.attrs.get('height', '400')
output = super(LocationEditWidget, self).render(name, '%s,%s,%s' % (value.latitude, value.longitude, value.address), attrs)
map_html = render_to_string('./select_location.html',
{
"id" : self.attrs['id'],
"geocoding_button" : self.attrs['geocoding'],
"geolocation_button": self.attrs['geolocation'],
"latitude" : self.attrs['latitude'],
"longitude" : self.attrs['longitude'],
"width" : self.attrs['width'],
"height" : self.attrs['height']
})
return mark_safe(output+map_html)
The idea is that any other model that need to work with locations, has a ForeignKey field associated with the Location model, for example:
class Event(models.Model):
"""
`Events` main model.
"""
title = models.CharField(_('title'), max_length=100, blank=True)
""" Title. """
place = models.ForeignKey(Location, verbose_name=_('place'), related_name="place", blank=True)
""" Location. """
meeting_point = models.ForeignKey(Location, verbose_name=_('place'), related_name="meeting_point", blank=True)
""" Meeting Point """
duration = models.PositiveIntegerField(_('duration days'), blank=True)
""" Duration, specified in days. """
price = models.DecimalField(_('price'), max_digits=7, decimal_places=2, blank=True)
""" Price. """
So when you send the form (apart from save conventionally all the other model fields), a new Location object will be created with the provided location information (latitude, longitude and address) and it will be stored in the database. Finally establishing the relationship with the ForeignKey field ("place" in the previous model) using this new Location object.
For now, the widget works correctly and renders the map instead of the typical select widget, besides filling the HiddenInput with latitude, longitude and direction.
In my events/forms.py:
class NewEventForm(forms.ModelForm):
def save(self):
value = self.data['place'].split(',');
location = Location(latitude=value[0], longitude=value[1], address=value[2:])
self.instance.place = location
new_event = super(NewEventForm, self).save(commit=False)
new_event.save()
return new_offer
class Meta:
model = Event
fields = ('title', 'duration', 'price', 'place')
widgets = {
'place': LocationEditWidget(),
}
and in my events/views.py:
#login_required
def event_new(request):
new_event = Event(price=0, owner=request.user)
if request.method == 'POST':
# Form submited
Event_form = NewEventForm(request.POST, instance=new_event)
if event_form.is_valid():
# Form succesfully validated
event_form.save()
return HttpResponseRedirect('/Event/%s/edit' % new_event.id)
else:
# Initial form
event_form = NewEventForm(instance=new_event)
return render_to_response( 'events/event_new.html',
{'event_form':event_form},
context_instance=RequestContext(request))
But I am new to Django and I have some problems to understand the relationships between widgets, forms, fields and form fields. The problem is that the form does not validate correctly and if I force validation (bypasing 'event_form.is_valid ()') I get an error when saving to the database. On the other hand does not seem quite right (Because of DRY) having to subclassing a ModelForm for each application/model that uses locations.
Do I need to create custom fields and custom form fields?
Any ideas as how to orient it?
Can I use a widget to a ForeingKey?
Thanks in advance and please forgive me for my poor English.

Categories

Resources