Django RF: POST geometry from Leaflet Draw to PostGIS - django

I'm trying to store some geometry in a PostGIS DB which is created using Leaflet Draw.
The following answer only covers the first part: how to transform the drawn shape into GeoJSON, i.e.:
map.on(L.Draw.Event.CREATED, function (e) {
var type = e.layerType
var layer = e.layer;
// Do whatever else you need to. (save to db, add to map etc)
drawnItems.addLayer(layer);
//Export to DB (source: https://stackoverflow.com/a/24019108/3976696)
var shape = layer.toGeoJSON()
shape_for_db = JSON.stringify(shape);
For example, shape_for_db contains:
{
"type": "Feature",
"properties": {},
"geometry": {
"type":"Polygon",
"coordinates":[[[-0.217073,51.918784],[-0.361362,51.101904],[-0.96918,53.4925],[-0.217073,51.018784]]]
}
}
However, I can't find any solution to successfully insert the geometry in the DB.
So far, based on this, I've tried the following:
$.ajax({
type: "POST",
dataType: "json",
contentType: "application/json",
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
url: "/en/api/geomtable/",
data: JSON.stringify({"description":"this is a test", "geom":shape_for_db}),
success : function(result) {
console.log(result);
}
});
This returns a 400 Bad Request with the following error:
Unable to convert to python object: Invalid geometry pointer returned
from "OGR_G_CreateGeometryFromJson"."
What should I make of this error? Should I parse the GeoJSON first or something?
UPDATE:
I narrowed a bit the problem, but still haven't succeeded to store GeoJSON in a PostGIS geometry column.
First, I figured out how to do it via SQL:
INSERT INTO citydb.cityobject (gmlid, objectclass_id, envelope)
VALUES
(
'This is a test insert',
23,
ST_SetSRID(ST_GeomFromGeoJSON
(
'{
"type":"Polygon",
"coordinates":[
[7.814375,52.743552],
[12.9375,51.886624],
[7.375,52.520452]
]
}'
),2056)
)
Next, I confirmed that the Ajax above successfully posts non-geometry data to PostgreSQL (i.e. when I remove "geom":shape_for_db from the posted geojson), and fails as soon as I include the geometry (i.e. I get 400 Bad request).
Therefore, I suppose I have to setup my serializer or something in Django to do the equivalent of ST_SetSRID(ST_GeomFromGeoJSON(...)).
I found that the equivalent in Django should be GEOSGeometry, but I am quite unsure on how/where to set this up.
Could anyone provide some guidance on how to set up this
"geojson-to-geometry" conversion?
And more generally, should I really be doing this conversion in the Django Rest Framework, or are there compelling reasons to do it in the Javascript part, or even directly in the PostgreSQL database (e.g. a function triggered when a geojson is written in the geometry column)?
Currently, my Django/DRF setup is as follows:
views.py
#Viewsets and serializers based on https://www.django-rest-framework.org/tutorial/quickstart/
class CityobjectViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Cityobject information to be viewed or edited.
"""
lookup_field = 'id'
queryset = Cityobject.objects.all()
serializer_class = CityobjectSerializer
# filter_backends = [DjangoFilterBackend] #allows Django-ORM filters in URL
filter_fields = ('id','gmlid','name',)
urls.py
router = routers.DefaultRouter()
router.register(r'cityobject', views.CityobjectViewSet, 'cityobject')
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
serializers.py
class CityobjectSerializer(GeoFeatureModelSerializer):
class Meta:
model = Cityobject
#Provide only relevant fields for frontend
fields = (
"objectclass",
"gmlid",
"name",
"description",
"envelope",
"creation_date",
"xml_source"
)
geo_field = 'envelope'

Related

Save Django Model when list_editable fields are changed in Django Admin change_list view

I have a Django Model which I'm trying to update from the Admin change_list view when a field that is listed in list_editable has a change event fired. Right now the only way to save the updates is by clicking a "Save" button and then the page reloads. I don't want the page to reload at all. I just want it to update the model asynchronously.
For example I have the following model.
class Example(models.Model):
name = models.CharField()
hide = models.BooleanField(default=False)
In the Admin Form I have
class ExampleAdmin(admin.ModelAdmin):
list_display = [
"name",
"hide"
]
list_editable = [
"name",
"hide"
]
When viewing the change list for Examples now I will see the values listed out with an editable text input for name and a checkbox for hide.
I would like to listen for events on those inputs and when they are fired send an async request to update the specific model.
UPDATE
This is what I have right now.
window.addEventListener("load", function() {
(function($) {
$(".field-name .vTextField").on("change",function (event) {
$('#changelist-form').submit()
})
$('#changelist-form').submit(function(event) { // On form submit event
$.ajax({ // create an AJAX call...
data: $(this).serialize(), // get the form data
type: "POST", // GET or POST
url: $(this).attr('action'), // the file to call
success: function(response) { // on success..
console.log(response)
},
error: function(e, x, r) { // on error..
console.log(e)
}
});
event.preventDefault()
return false;
});
})(django.jQuery);
})
I can see this firing in the network requests and can verify that the serialized data being sent is updated. The response is a 200, but when I refresh the page the data has not been updated.
UPDATE
At this point I'm giving up on sending this data to Django and will be sending the serialized data to a custom endpoint and parse through and save the data in a custom save function.
Within your Admin Model add the following
class ExampleAdmin(admin.ModelAdmin):
class Media:
js = ("js/admin/example.js")
list_display = [
"name",
"hide"
]
list_editable = [
"name",
"hide"
]
example.js
window.addEventListener("load", function() {
(function($) {
$(".field-name .vTextField").on("change",function (event) {
submit()
})
function getFormData($form){
var unindexed_array = $form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
function submit(){
$.ajax({
data: getFormData($('#changelist-form')),
type: "POST",
url: '/api/save-example',
success: function(response) {
console.log(response)
},
error: function(e, x, r) {
console.log(e)
}
});
}
})(django.jQuery);
})
I'm using Django Rest Framework so I created a POST endpoint, but you could also do this in a Django View that is listening for a POST.
When you execute the post Django will send the data categorized by the row number and field name, like so.
form-0-id : 1
form-0-name: example-name
form-0-hide: false
form-0-example_parent: 100
form-1-id : 2
form-1-name: example-name-two
form-1-hide: true
form-1-example_parent: 200
#api_view(["POST"])
def save_example(request):
try:
total_rows = int(request.POST.get("form-TOTAL_FORMS"))
prefix = "form-"
form_fields = {
"id": "id",
"name": "name",
"hide": "hide",
"example_parent": "example_parent_id",
}
for x in range(total_rows):
item = {}
for key, val in form_fields.items():
post_field = prefix + str(x) + "-" + key
field = val
if key == "id":
id = request.POST.get(post_field)
else:
item[field] = request.POST.get(post_field)
Example.objects.filter(id=id).update(**item)
return Response(status=status.HTTP_200_OK)
except Exception as e:
return Response(status=status.HTTP_404_NOT_FOUND)
Note
total_rows = int(request.POST.get("form-TOTAL_FORMS")) which lets you know how to loop through all the form data that is passed back.
This doesn't take different values into consideration. (i.e. Checkboxes will return an on value but no value if not checked.
You will need to update your save_example function accordingly.

Django Server Side set-up for pagination with Datatable

I am using Django for backend, and I am using Datatable library to display a large number of records ( approx 1 million records ). I am trying to set up datatable in such a way that
every time 25 records are being fetched from the backend and when the user clicks on the next page button, another ajax call gets the next 25 records and so on.
But I am having a trouble setting these up.
My DataTable initialisation :
$("#company-table").DataTable({
"processing": true,
"serverSide": true,
"bDestroy": true,
ajax: {
type: "POST",
url: "/get_results/",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
data: {
...other data params...
page:$("#company-table").DataTable().page()
},
},
columns: [
...populating columns...
],
});
And my views.py looks like this ( It is completely wrong as far as I know ) :
#filtered_queryset contains all the records.
paginator = Paginator(filtered_queryset, 25) # Show 25 contacts per page.
page_number = request.POST.get('page')
start = request.POST.get('start')
length = request.POST.get('length')
page_obj = paginator.get_page(page_number)
data = list(page_obj.object_list.values())
return_data = {"data": data}
json_data = json.dumps(return_data,indent=4, sort_keys=True, default=str)
return HttpResponse (json_data, content_type = "application/json")
Can anyone help me? Or just nudge me in the right direction?
Try this one django-ajax-datatable
I hope this will work

Django elasticsearch dsl completion field issue

I am trying to implement search suggestions using django-elasticsearch-dsl-drf for streets.
This is documents.py:
class StreetDocument(Document):
id = fields.IntegerField()
name_ru = StringField(
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField(),
}
)
... # same for name_uz and name_oz
tags_ru = fields.CompletionField()
... # same for tags_uz and tags_oz
class Django:
model = Street
fields = (
'code',
'street_type'
)
in views.py I have this:
from django_elasticsearch_dsl_drf.constants import SUGGESTER_COMPLETION
from django_elasticsearch_dsl_drf.filter_backends import SuggesterFilterBackend, CompoundSearchFilterBackend
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet
class SuggestionListAPI(DocumentViewSet):
document = StreetDocument
serializer_class = StreetDocumentSerializer
filter_backends = [
CompoundSearchFilterBackend,
SuggesterFilterBackend,
]
search_fields = (
'code',
'name_ru',
'name_uz',
'name_oz',
'tags_ru',
'tags_uz',
'tags_oz'
)
suggester_fields = {
'name_ru_suggest': {
'field': 'name_ru.suggest',
'suggesters': [
SUGGESTER_COMPLETION,
],
},
... # same for name_uz and name_oz
'tags_ru': {
'field': 'tags_ru',
'suggesters': [
SUGGESTER_COMPLETION,
],
},
... # same for tags_uz and tags_oz
}
Request to /suggestions?name_ru_suggest__completion=tolstoy does nothing, just receiving all streets unfiltered.
Request to /suggestions?search=tolstoy works great, but I need autocompletion.
Where did I go wrong? And that will be great if it's possible to use two fields for suggestion at once.
Thanks for your time and help.
It looks like you're using wrong endpoint for suggestions. Correct name is suggest.
Example: http://127.0.0.1:8000/search/publishers/suggest/?country_suggest__completion=Ar
Corresponding viewsets
As of your question about using two fields for suggestion at once, it's possible to get suggestions for multiple fields at once. For instance, if you have title and author, both properly indexed and configured, you could query it as follows:
http://127.0.0.1:8000/search/books/suggest/?title_suggest=wa&author_suggest=le
This will, however, return you the following result:
{
"title_suggest": [...],
"author_suggest": [...]
}
However, if you need to refine your suggestions based on some criteria, you could do that using context suggesters (search for it in the official documentation of django-elasticsearch-dsl-drf).

POST 400: Bad request - Using Django REST API and React

I'm pretty new to web development so please forgive me in advance for my ignorance.
I'm using React to try to post data to server endpoint managed by Django using this method:
sendData(data) {
const url = "http://127.0.0.1:8080/api/filtros/1/";
const requestOptions = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(data)
};
fetch(url, requestOptions);
}
On the onClick of a NavDropdown React component:
<NavDropdown.Item
key={item.id}
onClick={() =>
this.sendData({
id: 0,
dimension_id: dimension.id,
item_id: item.id,
usuario_id: 1
})
}
>
{item.descripcion}
</NavDropdown.Item>
This is how I register the url on the router using Django:
router.register('api/filtros/1', FiltroUsuariosViewSet, 'filtro')
My Django ModelViewSet looks like this:
class FiltroUsuariosViewSet(viewsets.ModelViewSet):
queryset = FiltroUsuarios.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = FiltroUsuariosSerializers
And my Django Serializer looks like this:
class FiltroUsuariosSerializers (serializers.ModelSerializer):
class Meta:
model = FiltroUsuarios
fields = ('id', 'dimension_id', 'item_id', 'usuario_id')
def create(self, validated_data):
post = FiltroUsuarios.objects.create(**validated_data)
When I click on the Component I get this:
POST http://127.0.0.1:8080/api/filtros/1/ 400 (Bad Request)
and apparently the error is on the fetch request.
Do you guys have any idea on whats the problem?
Thanks a lot in advance!
The best way to understand and get rid of 400 Bad Request errors when wiring Django and React, is to run Django in development mode and then fire up your browser's Network tab while sending the request.
Switch into the Network -> Response tab and call sendData(). Since you are running on Django's development server, you will get the specific exception on your 400 Bad Request error. To simulate this, see the screenshot below and notice:
{"user": ["Incorrect type. Expected pk value, received str."]}
Back to your problem, you have the following in your .sendData():
x = {
id: 0,
dimension_id: dimension.id,
item_id: item.id,
usuario_id: 1
}
Which you then call JSON.stringify() on. If dimension.id and item_id are both integer (a reasonable assumption), then you're passing the following as a payload:
JSON.stringify(x)
# returns:
"{"id":0,"dimension_id":1,"item_id":2,"usuario_id":3}"
Your Django Model for FiltroUsuarios defined these columns / fields, so you now need to check both your models and FiltroUsuariosSerializers that these are expected value / value types mapping to these columns.

Django: combining method and class inside views to share same url

I'm working on Django project where I have an app which has a page with a drop down and a chart that is generated from data (which is queried from the database and passed into an API url). I'm using class based views and APIView and have a get method which is creating a response to pass some Json data into a url. I have the following class in my views.py set up and working.
views.py
class ChartData(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, format=None):
#generated through code
data = {
"labels": all_times,
"default": all_data,
}
return Response(data)
In order have access to this data, I have my url set as:
url(r'^display/api/chart/data/$', views.ChartData.as_view()),
This data from the API is used to generate a ChartJS graph in my template html of this app. The chart is set up so that it seeks the Json data from the url display/api/chart/data/ and uses Ajax to populate itself. Here is how the chart is generated:
<script>
{% block jquery %}
var endpoint = 'display/api/chart/data/'
var defaultData = []
var defaultLabels = [];
$.ajax({
method: "GET",
url: endpoint,
success: function(data){
defaultLabels = data.labels
defaultData = data.default
console.log(data)
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: defaultLabels,
datasets: [{
label: '# Measurable',
data: defaultData,
borderWidth: 1
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
},
error: function(error_data){
console.log("error")
console.log(error_data)
}
})
{% endblock %}
</script>
I also have another method in the same views file as follows:
views.py
def DropdownDisplay(request):
items= #query from database
if (request.method == 'POST' and request.is_ajax()):
print(request.POST)
return render(request, 'DisplayData/Display.html',{'dropdown':items})
This method is used to generate a dropdown in the page. The url for this is set up in the same urls.py file as follows:
url(r'^graph/', views.DropdownDisplay, name='Display-Dropdown')
Note: they are in the same views.py and urls.py file, I just separated them in this post for formatting purposes.
Now, I'm running into an issue. When I go to /display, the graph works, but the drop down does not. When I go to /display/graph, the drop down works but the graph does not. I can tell it is because in my urls.py, I have separate urls for each class and method, so they only work under their respective url.
However, I would like to combine them so that both the generation of the graph, and the population of the dropdown, work under one url, which will be /display.
How can I go about achieving this?
If you want them to be a part of the same page, both the functionalities should be inside a form under one view. If not, there should not be one single url for two separate functionalities. Your question is a bit ambiguous. If you can clearly state the functionality, would be helpful.