Django Rest Framework + Angular 2 - uploading multiple files - python

I'm using Django Rest Framework as my backend and Angular 2 for my frontend. I've got this page in Angular, where I create a form:
createResourcesForm() {
this.resourcesForm = this.formBuilder.group({
resources: this.formBuilder.array([
this.formBuilder.group({
title: ['', Validators.compose([Validators.required])],
file: ['', Validators.compose([])],
})
])
})
}
As you can see, the form consists of FormArray, where every element has two inputs: title and file - a text input and a file input respectively. On submitting the form I'm trying to send the data to Django but I get an error Missing filename. Request should include a Content-Disposition header with a filename parameter.. I could set it easily but I'm expecting to receive a list of {title, file}, so how to set multiple file names? Any other idea how I could do this?
The error in Django Rest Framework comes from parse method in FileUploadParser.
I'm not pasting any Python code here because it's a standard ListCreateAPIView, nothing special about it. Here is my serializer:
class ResourceCreateSerializer2(serializers.Serializer):
author_pk = serializers.IntegerField(required=False)
author_first_name = serializers.CharField(max_length=50, required=False)
author_last_name = serializers.CharField(max_length=50, required=False)
resources = ResourceWithoutAuthorSerializer(many=True)
class ResourceWithoutAuthorSerializer(serializers.ModelSerializer):
instruments = InstrumentSerializer(many=True)
class Meta:
model = MusicResource
fields = ['title', 'file', 'instruments']
Don't mind the other fields, they are being sent just fine (as the file does). One more thing to say - I'm adding a content-type header in Angular just before sending the data.
UPDATE 1
Here is my method for uploading files (Angular 2):
get value() {
let formData = new FormData();
for (let i = 0; i < this.resources.length; i++) {
let resource = this.resources.value[i];
let fileName = resource.file;
let fileInputs = $('input[type="file"]').filter(function () {
return this.value == fileName;
});
if (fileInputs.length == 0) {
return null;
}
let fileInput = <HTMLInputElement>fileInputs[0];
formData.append('resources-' + i + '-title', resource.title);
formData.append('resources-' + i + '-file', fileInput.files[0], fileInput.files[0].name);
for (let j = 0; j < this.instrumentsSelect.value.length; j++) {
formData.append('resources-' + i + '-instruments', this.instrumentsSelect.value[j]);
}
}
return formData;
}
then
this.musicResourcesService.addMusicResource(toSend).subscribe(
data => console.log('successfuly added resources'),
err => console.log('MusicResourcesAddComponent', 'onMusicResourceFormSubmit', err)
);
addMusicResource(data) {
let headers = new Headers({});
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({headers});
return this.api.post('resources/resources/list_create/', data, true, options);
}
public post(url: any, payload: any, noToken?, options?: any): Observable<any> {
const provider = noToken ? this.http : this.authHttp;
const fulLUrl = this.conf.getAPIUrl() + url;
return provider.post(fulLUrl, payload, options)
.delay(100)
.map(this.extractData)
.catch(this.handleError).share();
}

I did not like #Robert's answer and did not receive any other idea so after hours of reseraching it turns out that I was missing two things:
The parser should have been set to MultiPartParser, not FileUploadParser
There is no need to set the Content-Type header manually, it will get filled automatically along with the boundary which I was missing
Also, to make sure Django receives all the data and understands it, I had to change
formData.append('resources-' + i + '-title', resource.title);
and similar lines to
formData.append('resources[' + i + ']title', resource.title);

Related

Groovy Script AEM Asset Reference Update

So I'm working with AEM and am attempting to create a script that grabs all pages under a specific path and updates the image reference on the page from a list of assets under a curtain path.
Both of my select query's aren't returning the specific pages and assets I need.
I'm also getting an error that my queries are searching over 100000 Nodes
How can i resolve this error and query my resources better?
import com.day.cq.wcm.api.Page
import javax.jcr.query.Query
import javax.jcr.query.QueryManager
import org.apache.sling.api.resource.ModifiableValueMap
import groovy.transform.Field
static void main(String[] args)
{
String[] assetNodes
String[] pageNodes
String pagePath ="/content/we-retail/us/en"
String pageResourceType = "weretail/components/structure/page"
String assetPath ="/content/dam/microsoft/internal/en"
String assetQuery = "b1048291-23fa-422a-a7c4-9ea4bae0effc"
boolean isAsset = true;
pageNodes = GetResourcePath(pagePath, pageResourceType);
assetNodes = GetRosourceAsset(assetPath, assetQuery);
InputAssetsOnPage(pageNodes,assetNodes);
}
//Find the Node paths for all Pages to modify
//Narrow down to image component
def GetResourcePath(String rootPath,String queryParam)
{
int i = 0
def String[] allNodes = new String[500]
Page page = getPage(rootPath)
def queryManager = session.workspace.queryManager;
def param= queryParam;
def statement = 'select * from nt:base where jcr:path like \''+page.path+'/%\' and sling:resourceType = \'' + param + '\'';
Query query=queryManager.createQuery(statement, 'sql');
final def result = query.execute()
println "Total pages found = " + result.nodes.size();
NodeIterator nodeIterator = result.getNodes();
while(nodeIterator.hasNext())
{
def hitNode = nodeIterator.nextNode();
allNodes[i] = hitNode.getPath();
i++;
}
println allNodes
return allNodes;
}
//Find all assets paths to add to page
def GetRosourceAsset(String rootPath,String queryParam)
{
int i = 0
def String[] allNodes = new String[500]
Page page = getPage(rootPath)
def queryManager = session.workspace.queryManager;
def param= queryParam;
def statement = 'select * from nt:base where jcr:path like \''+rootPath+'/%\'';
Query query=queryManager.createQuery(statement, 'sql');
final def result = query.execute()
println "Total Assets found = " + result.nodes.size();
NodeIterator nodeIterator = result.getNodes();
while(nodeIterator.hasNext())
{
def hitNode = nodeIterator.nextNode();
allNodes[i] = hitNode.getPath();
i++;
}
println allNodes
return allNodes;
}
//Modify image component property with unique asset path
void InputAssetsOnPage(String[]pageRefrence, String[]assetRefrences)
{
String[] nodes= pageRefrence;
String[] assetNodes = assetRefrences;
nodes.eachWithIndex { self,i->
javax.jcr.Node node=getNode(nodes[i])
ModifiableValueMap mVMap=resourceResolver.resolve(node.path).adaptTo(ModifiableValueMap.class);
mVMap.put("fileRefrence", assetNodes[i]);
println "Property modified to "+node.path;
println "Dry Run "+data.dryRun;
if(!data.dryRun) {
session.save();
}
}
}
//Save session
For mass-updates it is very likely to target too many nodes. You have to try some approaches, to either get just under the limit - or change you approach.
First select pages from cq:PageContent, instead from nt:base. The query-indexes are organised by jcr:primaryType. The nt:base index contains everything. So there are much more nodes.
Second use SQL-2 and ISDESCENDANTNODE() instead of a like operator. I don't expect the like-operator to be so specific. But, if you query almost all pages anyway - it won't help much.
Third iterate over parts of you page-tree. Then the remaining subtree is much small, can can be queried.
For a mass-update, which probably touches more than 15% of your pages, then just iterate over all pages. Don't forget to commit from time to time. (e.g. every 100 changes)
Code sample to iterate a page-tree
def rootPage = getPage("/content/uac/glooly/es")
rootPage.recurse() { page ->
println(page.path)
fixAssetsOnPage(page)
}
You can iterate over the first n levels over the content tree, and then deep dive with a query. So you could have less than 100.000 image components
Code sample to iterate over first 2 levels, and then query:
def rootPage = getPage("/content/we-retail/us/en")
rootPage.iterator().each { firstLevelPage ->
firstLevelPage.iterator().each { secondLevelPage ->
println(secondLevelPage.path)
queryAndFixAssets(secondLevelPage)
save()
}
}
Fourth create an oak:index for you query. Especially if you could use it for later too. But in your case there is an existing index for sling:resourceType, which should be could enough. You have just too many hits.
PS: You probably don't need to query pages. Just query the image components in a small enough subtree. But we can't see that from you code sample.

Django Form does not load MultipleChoiceField data from ajax POST request

I ve a weird issue with a MultipleChoiceField that does not return the items that are in the POST QueryDict
here is the Form
class TranslationLanguagesForm(forms.Form):
languages = forms.MultipleChoiceField(
widget=forms.SelectMultiple(attrs={"novalidate": "",}),
choices=languages,
required=False,
)
the View is something like (shortened):
class AjaxSpotlerCreateView(View):
def post(self,request):
# ...
# some code before
#
translation_form = TranslationLanguagesForm(
self.request.POST, prefix="translation"
)
if translation_form.is_valid():
translation_languages = translation_form.cleaned_data.get(
"languages"
)
#
# some code after
#
I won't write the whole template but the html created by the form fits what I expected:
<select name="translation-languages" novalidate="" class="form-control " id="id_translation-languages" multiple="">
<option value="fr">french</option>
<option value="en">english</option> <option value="es">spanish</option> </select>
The jquery that sends the data trough ajax request is given below:
function ajaxPOST() {
var dismiss = false;
$.ajax({
method: "POST",
url: ajaxURL,
data: getFormData(),
beforeSend: function () {},
success: function (data) {
$target.find(".modal-content").html(data);
if (data.length == 0) dismiss = true;
},
complete: function () {
if (dismiss) hideUploadModal();
else showUploadModal();
}, //complete
}); //ajax
}
function getFormData() {
const result = {};
const $form = $target.find("form#video-loader-form");
const $inputs = $form.find("input, select, textarea");
$inputs.each((_, element) => {
const $element = $(element);
const type = $element.attr("type");
const name = $element.attr("name");
if (name && type == "checkbox" && $element.prop("checked"))
result[name] = $element.prop("checked");
else if (name && type != "checkbox") result[name] = $element.val();
});
return result;
}
the issue is that the form is never "filled" by the data of request.POST and translation_languages receives always an empty list.
...but self.request.POST.getlist("translation-languages[]") returns the correct values
It only happened on MultipleChoiceField whereas ChoiceField returns the correct value
here are the POST data (you see more data than needed by the form with the issue because there are 4 forms and 1 formset in the view) :
<QueryDict: {'csrfmiddlewaretoken':
['bQQzHTR4JDFZFnmO1ExlPZhqURHswXTmXA9RGC2c05pBM63ns2gnVwUnbnwGzor1'],
'transcription-language': ['en-us'], 'translation-languages[]': ['fr',
'es'], 'spotlers-TOTAL_FORMS': ['1'], 'spotlers-INITIAL_FORMS': ['1'],
'spotlers-MIN_NUM_FORMS': ['0'], 'spotlers-MAX_NUM_FORMS': ['1000'],
'spotlers-0-url':
['https://test-dev-s.storage.googleapis.com/uploads/FR/5e24512/2021/1/9fccac26/9fc37a26-2545-434f-8bd2-0afc3df839aa_full.mp4?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=storage%40funky-tower-264412.iam.gserviceaccount.com%2F20210108%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20210108T125533Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&x-goog-signature=8e737cbc384fab5e11002cbc5e6308'], 'spotlers-0-id': ['9fc37a26-1893-434f-8bd2-0afc3df839ef'],
'spotlers-0-name': ['ultraclimb'], 'spotlers-0-duration':
['00:02:43'], 'spotlers-0-is_postedited': ['true'],
'keywords-keywords': [''], 'glossary-glossary': ['']}>
It seems also that the dict returned by ajax POST creates a weird name for multiselect. The name of the field is postfixed by array symbols : []...
So I got 'translation-languages[]': ['fr', 'es'] instead of having 'translation-languages': ['fr', 'es']
well finally I got it !
the problem was in the way I have built the POST data in the jquery.
I created a dict by collecting all inputs fields names and values but it returned a querydict with a wrong field name for array by postfix it with brackets.
I should rather use $form.serialize()
I've made that change and it's now working as expected
A bit late, but you can set the traditional parameter to true to the ajax call. This way, jQuery will use jQuery.param(yourObject, true) instead of simply jQuery.param(yourObject).
You keys referring to list of values will not be altered.
https://api.jquery.com/jQuery.param/
https://api.jquery.com/jquery.ajax/ (see at the data settings explanation)
Cheers,

Sort Swagger Tags

Hi I'm using flasgger/swagger but I was wondering if there are feature where in I could sort all my tags in alphabetical order? right I don't understand the order of my tags. It's neither Alpha nor Numeric. Sample order is like this
User
- API GET
- API POST
- API PUT
- API DELETE
Company
- API GET
- API POST
- API PUT
- API DELETE
Room
- API GET
- API POST
- API PUT
- API DELETE
So basically User, Company and Rooms are Swagger Tags. I would like to arrange it where in Company should come first then followed by Room then User. Is there a way to achieve this in swagger 2.0
Updates:
I would like it to be sorted in web browser display. In short on how we see the presentation of all this Tags in sorted order
For everyone who have problem with tag order, you can do it with javascript (sample is sorting alphabetically ascending, but you can change sort function as you wish):
$(document).ready(function () {
sort();
});
function sort() {
ascending = true;
var tbody = document.getElementById("resources");//ul
var rows = tbody.childNodes;//li
var unsorted = true;
while (unsorted) {
unsorted = false
for (var r = 0; r < rows.length - 1; r++) {
debugger;
var row = rows[r];
var nextRow = rows[r + 1];
var value = row.getAttribute("id");
var nextValue = nextRow.getAttribute("id");
if (ascending ? value > nextValue : value < nextValue) {
tbody.insertBefore(nextRow, row);
unsorted = true;
}
}
}
}
This JS you should register as it is shown in example bellow (Web api / c# / .net)
config.EnableSwagger(c =>
{
***
})
.EnableSwaggerUi(u =>
{
u.InjectJavaScript(typeof(Startup).Assembly, "yourNamespace.SwaggerExt.js");
}
);
JS file should be embedded resource.

Return JSON object to View with Flask and Ajax

I am trying to return a python dictionary to the view with AJAX and reading from a JSON file, but so far I am only returning [object Object],[object Object]...
and if I inspect the network traffic, I can indeed see the correct data.
So here is how my code looks like. I have a class and a method which based on the selected ID (request argument method), will print specific data. Its getting the data from a python discretionary. the problem is not here, have already just tested it. But just in case I will link it.
# method to create the directionary - just in case #
def getCourselist_byClass(self, classid):
"""
Getting the courselist by the class id, joining the two tables.
Will only get data if both of them exist in their main tables.
Returning as a list.
"""
connection = db.session.connection()
querylist = []
raw_sql = text("""
SELECT
course.course_id,
course.course_name
FROM
course
WHERE
EXISTS(
SELECT 1
FROM
class_course_identifier
WHERE
course.course_id = class_course_identifier.course_id
AND EXISTS(
SELECT 1
FROM
class
WHERE
class_course_identifier.class_id = class.class_id
AND class.class_id = :classid
)
)""")
query = connection.engine.execute(raw_sql, {'classid': classid})
for column in query:
dict = {
'course_id' : column['course_id'],
'course_name' : column['course_name']
}
querylist.append(dict)
return querylist
my jsonify route method
#main.route('/task/create_test')
def get_courselist():
#objects
course = CourseClass()
class_id = request.args.get('a', type=int)
#methods
results = course.getCourselist_byClass(class_id)
return jsonify(result=results)
HTML
and here is how the input field and where it should link the data looks like.
<input type="text" size="5" name="a">
<span id="result">?</span>
<p>click me
and then I am calling it like this
<script type=text/javascript>
$(function() {
$('a#link').bind('click', function() {
$.getJSON("{{ url_for('main.get_courselist') }}", {
a: $('input[name="a"]').val()
}, function(data) {
$("#result").text(data.result);
});
return false;
});
});
</script>
but every time I enter a id number in the field, i am getting the correct data. but it is not formatted correctly. It is instead printing it like [object Object]
source, followed this guide as inspiration: flask ajax example
The data return by your server is like: {result: [{course_id: 'xxx', course_name: 'xxx'}]}, in which data.result is a JS Array.
when you set it to $("#result").text(), JS convert a array to string, so the result is [object Object].
You should iterate over the array to construct a string, then set the string in DOM, like:
courseStr = data.result.map(function(course) {return course.course_id + '-' + course.course_name; }).join(',');
$("#result").text(courseStr);
The API description for flask.json.jsonify indicates it's expecting keyword parameters. What you actually want to do seems to be serialize a list object containing dictionaries, have you tried flask.json.dumps instead? Assuming you've got the dumps symbol imported, instead of your jsonify call you can try:
return dumps(results)

Extracting BIND parameters to build a JSON query

I have a file which was exported from BIND containing TSIG values for about 500 domain names. I need to repurpose the data into JSON for a REST API query. The BIND data is formatted like so:
// secondary-example.com.
key "2000000000000.key." {
algorithm hmac-md5;
secret "ahashedvalue=";
};
zone "secondary-example.com." {
type slave;
file "sec/secondary-example.com.";
allow-transfer { 1.1.1.1;
1.1.2.2;
};
also-notify { 1.1.1.1;
2.2.2.2;
};
masters {
1.2.3.4 key 2000000000000.key.;
};
};
From this I need to extract the key, zone and secret. Here's an example API request.
{
"properties":{
"name":"secondary-example.com.",
"accountName":"example",
"type":"SECONDARY"
},
"secondaryCreateInfo":{
"primaryNameServers":{
"nameServerIpList":{
"nameServerIp1":{
"ip":"1.2.3.4",
"tsigKey":"2000000000000.key.",
"tsigKeyValue":"ahashedvalue="
}
}
}
}
}
I'm having difficulty crafting a regular expression appropriate for the scenario. I'm looking construct the JSON in a python script and send the request through Postman.
I spent a couple days reading up on regex and figured out a solution. So, each of those "zones" began with a comment... e.g. "secondary-example.com"... and each set of BIND info was 17 lines long exactly. This solution is hackey and always assumes data is correct, but it managed to work.
Separate the zones into chunks of text.
zones = []
cur_zone = ''
f = open(bind_file).readlines()
for line in f:
if line[0:2] == '//':
zones.append(cur_zone)
cur_zone = ''
else:
cur_zone = cur_zone + line
zones.pop(0) # Drop the first list item, it's empty
Iterate through those chunks and match the needed parameters.
for z in zones:
z_lines = z.splitlines()
# Regex patterns to make the required parameters
key = re.findall('\"(.*)\"', z_lines[0])[0]
secret = re.findall('\"(.*)\"', z_lines[2])[0]
name = re.findall('\"(.*)\"', z_lines[5])[0]
master = re.findall('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', z_lines[15])[0]

Categories