问:
Iterate over another url with a for loop in django
I'm setting up a button that save API datas into my database and I'm trying to find a way to get the description out of every products but they are not included with the main url that get all products by city: (https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr), they are only included if we use this link which call a product by its id: (https://www.test-headout.com/api/public/v1/product/get/508).

So I was adviced to first hit the Listing API to get all products. Iterate over the list and get id and hit the product/get API to get the description.

This is what I've tried so far but I don't get how to do the iteration part:

My Django views:

```<code>def api_data(request):
    response = requests.get("https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr",
      headers={
        "Headout-Auth": HEADOUT_TEST_API_KEY
      }
    ).json()

    if (request.GET.get('mybtn')):
        for item in response['items']:  # for loop
            Product.objects.get_or_create(
                title=item['name'],
                destination=item['city']['name'],
                #description=response['contentListHtml'][0]['html'],
                link=item['canonicalUrl'],
                image=item['image']['url'],
            )

    return render(request, "form.html")
</code>```

How can I do this?

Based on @ralf answer this is what I came out with
:

```<code>def api_data(request):
    if (request.GET.get('mybtn')):
        url_1 = requests.get("https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr",
          headers={
            "Headout-Auth": HEADOUT_TEST_API_KEY
          }
        )

        base_url_2 = requests.get("https://www.test-headout.com/api/public/v1/product/get/",
          headers={
            "Headout-Auth": HEADOUT_TEST_API_KEY
          }
        )

        resp_data = url_1.json()

        for item in resp_data['items']:
            url = '{}{}'.format(base_url_2, item['id'])
            resp_data = url.json()

            Product.objects.get_or_create(
                title=item['name'],
                destination=item['city']['name'],
                description=response['contentListHtml'][0]['html'],
                link=item['canonicalUrl'],
                image=item['image']['url']
            )

    return render(request, "form.html")
</code>```

答:
Have you tried to make a GET request for each item? This can be done inside the for-loop that you already have.

Maybe like this:

```<code>import requests

url_1 = 'https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr'
base_url_2 = 'https://www.test-headout.com/api/public/v1/product/get/'
resp_data = requests.get(url_1).json()

for item in resp_data['items']:
    url = '{}{}'.format(base_url_2, item['id'])
    resp_data = requests.get(url).json()

    print('item')
    print(item)
    print('resp_data')
    print(resp_data)

    break

    Product.objects.get_or_create(...)
</code>```

Running the code for the first item (notice the break I put there for this test) I get the correct output:

```item
{'id': '7324', 'name': 'Billets Accès Rapide pour la Sagrada Familia', 'url': '/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'canonicalUrl': 'https://www.test-headout.com/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'city': {'name': 'Barcelona', 'code': 'BARCELONA'}, 'image': {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/eaeefed3-c87b-4531-82c0-0089dc8d11ec-7324-barcelona-sagrada-familia-skyline-day-01.jpg'}, 'neibhourhood': 'Barcelona', 'primaryCategory': {'id': 327, 'name': 'Sagrada Familia', 'cityCode': 'BARCELONA', 'url': '/category/327'}, 'currency': {'code': 'EUR', 'currencyName': 'Euro', 'symbol': 'EUR', 'localSymbol': '€', 'precision': 2, 'currency': 'EUR'}, 'startGeolocation': {'latitude': 41.403629302978516, 'longitude': 2.1743557453155518}, 'ratingCumulative': {'avg': 4.7, 'count': 2496}, 'pricing': {'type': 'PER_PERSON', 'currencyCode': 'EUR', 'minimumPrice': {'originalPrice': 17, 'finalPrice': 17}, 'bestDiscount': 0}}
resp_data
{'id': 7324, 'name': 'Fast Track Tickets to Sagrada Familia', 'url': '/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'canonicalUrl': 'https://www.test-headout.com/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'neighbourhood': 'La Sagrada Familia', 'city': {'name': 'Barcelona', 'code': 'BARCELONA'}, 'currency': {'code': 'EUR', 'name': 'Euro', 'symbol': 'EUR', 'localSymbol': '€', 'precision': 2}, 'displayTags': ['family friendly', 'sightseeing tours', 'day trips', 'attraction tickets', 'skip the line', 'city passes'], 'images': [{'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/eaeefed3-c87b-4531-82c0-0089dc8d11ec-7324-barcelona-sagrada-familia-skyline-day-01.jpg'}, {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/ee6912c4-1f4a-482b-be4e-5c035404da96-7324-barcelona-sagrada-familia-skyline-day-02.jpg'}, {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/d983bcd2-be68-4e0e-9066-82adb95f468c-7324-barcelona-sagrada-familia-skyline-day-03.jpg'}, {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/e8afec3d-acad-4056-b15c-f63f8fda9a22-7324-barcelona-sagrada-familia-skyline-day-04.jpg'}], 'contentListHtml': [{'title': 'Summary', 'type': 'SUMMARY_HTML', 'html': '
What to Expect
\n
Purchasing tickets for entry at the venue is not only time consuming but often times, futile. Since each time slot for entry into the basilica has limited capacity, same day entry tickets on site tend to sell out before mid-day, even if you waited in line for several hours, as many visitors must do. Our fast track Sagrada Familia tickets provide you with priority access to this iconic basilica. Once you book your tickets, you will receive them via email which you can then display on your phone to skip the ticketing line entirely and proceed directly to the security check.
\n
Before you head into the inner sanctum of the church, make sure to explore the outer walls and facades of the Sagrada Familia. Two of the three facades, Nativity, and Passion, have been completed, while the third, the Glory Facade, is still under construction. The Nativity facade tells the story of the birth of Jesus Christ while the Passion Facade is dedicated to his death. Though you can admire the facades and towers from the outside, these tickets do not give you access to the towers themselves. If you wish to gain entry to the towers along with the Basilica, please click here.
\n
As you move inside, you will come face to face with one of the most enchanting interiors of any church in the world. The vaults of the Basilica are as high as 70 meters in certain places and the beautiful, stained glass windows, constructed as per Gaudi’s plans, as well as the pillars holding up the ceiling,  make the entire space appear as though it were the hollowed out rib cage of some giant celestial beast. The magical natural light filtering through the basilica’s exquisite windows will take your breath away and transport you to a different time.
'}, {'title': 'Highlights', 'type': 'HIGHLIGHTS_HTML', 'html': '
\n
Get a chance to visit the most iconic monument in Barcelona that has been under construction since 1882, this UNESCO World Heritage Site attracts more visitors each year than any other landmark in Spain.
\n
With these fast-track tickets that you can book online, you need not worry about the long winding queues and you will also be guaranteed access to an otherwise sold out attraction.
\n
Marvel at the magnificent inner sanctums of the basilica with its extraordinary vaulted ceilings and stained glass windows, bound to sweep you off your feet.
\n
'}, {'title': 'FAQs', 'type': 'FAQ_HTML', 'html': '
Know Before You Go
\n
Important Information
\n
Groups with more than a total of 9 people, either adults or children, will not be allowed to gain entry into the Basilica. Should you wish to visit with a group of more than 9 people, you must book a guided visit as per the rules of the Basilica. You can book this by clicking here.
\n
Timings
\n
Entry to the Sagrada Familia is timed. You will have the option to choose the time slot for your entry during the next step of your booking. Please note that you will only be allowed to gain entry at the time chosen during the booking so we recommend arriving 15 minutes in advance in order to ensure a hassle-free entry. Though your entry is timed, you may explore the basilica for as long as you wish. Most visitors spend around an hour and a half inside.
\n
November to February - 9:00 AM to 6:00 PM
\nMarch - 9:00 AM to 7:00 PM
\nApril to September - 9:00 AM to 8:00 PM
\nOctober - 9:00 AM to 7:00 PM
\nDecember 25, 26, January 1 and 6 - 9:00 AM to 2:00 PM

\n
Reduced Price Tickets
\n
Entry for children aged eleven and below is free, however, they will not receive an audioguide. You will have the option to choose the number of children who require free entry at the time of checkout.
\n
Getting There
\n
To get to the Sagrada Familia, you can take any of the following options:
\nBy Metro: Line 2 and Line 5 to the Sagrada Familia Station.
\nBy Bus: Buses 19, 33, 34, 43, 44, 50, 51, B20 and B24 will stop at the Sagrada Familia.

\n
The main access to the Sagrada Familia is on the Carrer de la Marina (C/Marina).
\n
Dress Code
\n
To pay your respects to the Holy family, you must be appropriately covered to enter this sacred monument. No tank tops, strapless shirts, short shorts or sandals will be accepted. You will be denied entry if this dress code is not adhered to.
\n
Accessibility
\n
The insides of the Sagrada Familia is wheelchair accessible. \nHowever, due to the narrow stairways leading to the towers, tower access is not suitable for individuals with mobility impairments.
\n
Only small backpacks and regular-size handbags are allowed inside. Guests with larger bags will not be allowed to enter (as there are no storage facilities).
'}, {'title': 'Cancellation Policy', 'type': 'CANCELLATION_HTML', 'html': '
Strict Cancellation Policy
\n
These tickets cannot be cancelled, amended or rescheduled.
'}, {'title': 'Ticket Delivery Information', 'type': 'TICKET_DELIVERY_INFO_HTML', 'html': '
You will receive an email confirmation with your Sagrada Familia mobile tickets instantly. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.
'}, {'title': 'Inclusions', 'type': 'INCLUSIONS_HTML', 'html': '
\n
Skip-the-Line access to the Sagrada Familia.
\n
'}, {'title': 'Exclusions', 'type': 'EXCLUSIONS_HTML', 'html': '
\n
Tower access.
\n
'}], 'content': [{'title': 'Summary', 'type': 'SUMMARY_HTML', 'html': '
What to Expect
\n
Purchasing tickets for entry at the venue is not only time consuming but often times, futile. Since each time slot for entry into the basilica has limited capacity, same day entry tickets on site tend to sell out before mid-day, even if you waited in line for several hours, as many visitors must do. Our fast track Sagrada Familia tickets provide you with priority access to this iconic basilica. Once you book your tickets, you will receive them via email which you can then display on your phone to skip the ticketing line entirely and proceed directly to the security check.
\n
Before you head into the inner sanctum of the church, make sure to explore the outer walls and facades of the Sagrada Familia. Two of the three facades, Nativity, and Passion, have been completed, while the third, the Glory Facade, is still under construction. The Nativity facade tells the story of the birth of Jesus Christ while the Passion Facade is dedicated to his death. Though you can admire the facades and towers from the outside, these tickets do not give you access to the towers themselves. If you wish to gain entry to the towers along with the Basilica, please click here.
\n
As you move inside, you will come face to face with one of the most enchanting interiors of any church in the world. The vaults of the Basilica are as high as 70 meters in certain places and the beautiful, stained glass windows, constructed as per Gaudi’s plans, as well as the pillars holding up the ceiling,  make the entire space appear as though it were the hollowed out rib cage of some giant celestial beast. The magical natural light filtering through the basilica’s exquisite windows will take your breath away and transport you to a different time.
'}, {'title': 'Highlights', 'type': 'HIGHLIGHTS_HTML', 'html': '
\n
Get a chance to visit the most iconic monument in Barcelona that has been under construction since 1882, this UNESCO World Heritage Site attracts more visitors each year than any other landmark in Spain.
\n
With these fast-track tickets that you can book online, you need not worry about the long winding queues and you will also be guaranteed access to an otherwise sold out attraction.
\n
Marvel at the magnificent inner sanctums of the basilica with its extraordinary vaulted ceilings and stained glass windows, bound to sweep you off your feet.
\n
'}, {'title': 'FAQs', 'type': 'FAQ_HTML', 'html': '
Know Before You Go
\n
Important Information
\n
Groups with more than a total of 9 people, either adults or children, will not be allowed to gain entry into the Basilica. Should you wish to visit with a group of more than 9 people, you must book a guided visit as per the rules of the Basilica. You can book this by clicking here.
\n
Timings
\n
Entry to the Sagrada Familia is timed. You will have the option to choose the time slot for your entry during the next step of your booking. Please note that you will only be allowed to gain entry at the time chosen during the booking so we recommend arriving 15 minutes in advance in order to ensure a hassle-free entry. Though your entry is timed, you may explore the basilica for as long as you wish. Most visitors spend around an hour and a half inside.
\n
November to February - 9:00 AM to 6:00 PM
\nMarch - 9:00 AM to 7:00 PM
\nApril to September - 9:00 AM to 8:00 PM
\nOctober - 9:00 AM to 7:00 PM
\nDecember 25, 26, January 1 and 6 - 9:00 AM to 2:00 PM

\n
Reduced Price Tickets
\n
Entry for children aged eleven and below is free, however, they will not receive an audioguide. You will have the option to choose the number of children who require free entry at the time of checkout.
\n
Getting There
\n
To get to the Sagrada Familia, you can take any of the following options:
\nBy Metro: Line 2 and Line 5 to the Sagrada Familia Station.
\nBy Bus: Buses 19, 33, 34, 43, 44, 50, 51, B20 and B24 will stop at the Sagrada Familia.

\n
The main access to the Sagrada Familia is on the Carrer de la Marina (C/Marina).
\n
Dress Code
\n
To pay your respects to the Holy family, you must be appropriately covered to enter this sacred monument. No tank tops, strapless shirts, short shorts or sandals will be accepted. You will be denied entry if this dress code is not adhered to.
\n
Accessibility
\n
The insides of the Sagrada Familia is wheelchair accessible. \nHowever, due to the narrow stairways leading to the towers, tower access is not suitable for individuals with mobility impairments.
\n
Only small backpacks and regular-size handbags are allowed inside. Guests with larger bags will not be allowed to enter (as there are no storage facilities).
'}, {'title': 'Cancellation Policy', 'type': 'CANCELLATION_HTML', 'html': '
Strict Cancellation Policy
\n
These tickets cannot be cancelled, amended or rescheduled.
'}, {'title': 'Ticket Delivery Information', 'type': 'TICKET_DELIVERY_INFO_HTML', 'html': '
You will receive an email confirmation with your Sagrada Familia mobile tickets instantly. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.
'}, {'title': 'Inclusions', 'type': 'INCLUSIONS_HTML', 'html': '
\n
Skip-the-Line access to the Sagrada Familia.
\n
'}, {'title': 'Exclusions', 'type': 'EXCLUSIONS_HTML', 'html': '
\n
Tower access.
\n
'}], 'startLocation': {'geolocation': {'latitude': 41.403629302978516, 'longitude': 2.1743557453155518}, 'address': {'addressLine1': 'Sagrada Família, Carrer de la marina', 'addressLine2': None, 'cityName': 'Barcelona', 'postalCode': '08013', 'state': 'Catalunya', 'countryName': 'Spain'}}, 'endLocation': {'geolocation': {'latitude': 41.403629302978516, 'longitude': 2.1743557453155518}, 'address': {'addressLine1': 'Sagrada Família, Carrer de la marina', 'addressLine2': None, 'cityName': 'Barcelona', 'postalCode': '08013', 'state': 'Catalunya', 'countryName': 'Spain'}}, 'productType': 'ATTRACTION', 'ratingCumulative': {'avg': 4.7, 'count': 2496}, 'hasInstantConfirmation': True, 'hasMobileTicket': True, 'variants': [{'id': 13376, 'name': 'Fast Track', 'description': '- Get Fast Track Access that lets you bypass the ticket line & directly join the security line\r\n- Get guaranteed timed entry to the basilica\r\n- Mobile vouchers for convenient access', 'duration': 5400000, 'inventoryType': 'FIXED_START_FLEXIBLE_DURATION', 'pax': {'min': 1, 'max': 9}, 'cashback': {'value': 10.0, 'type': 'PERCENTAGE'}, 'ticketDeliveryInfoHtml': '
You will receive an email confirmation with your Sagrada Familia mobile tickets within 30 minutes of your purchase. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.
\n
Please note that, in some rare cases, if the requested time slot is unavailable, we will book the next available time slot within 30 minutes of your preferred entry time. Please check the exact entry time on your tickets once you receive them.
', 'inputFields': [{'oldId': 42249, 'id': 'NAME', 'name': 'Full Name', 'dataType': 'STRING', 'validation': {'regex': '\\s*[^\\s]+\\s+[^\\s]+.*', 'minLength': 3, 'maxLength': 80, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42250, 'id': 'EMAIL', 'name': 'Email', 'dataType': 'STRING', 'validation': {'regex': "(?i)^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+(\\s*,\\s*((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+)*$", 'minLength': 5, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42251, 'id': 'PHONE', 'name': 'Phone', 'dataType': 'STRING', 'validation': {'regex': None, 'minLength': None, 'maxLength': 40, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}]}, {'id': 13377, 'name': 'Fast Track + Audioguide', 'description': '- Audioguide (Spanish, Catalan, Chinese, English, French, German, Italian, Japanese, Portuguese, Russian)\r\n- Get Fast Track Access that lets you bypass the ticket line & directly join the security line\r\n- Get guaranteed timed entry to the basilica\r\n- Mobile vouchers for convenient access', 'duration': 5400000, 'inventoryType': 'FIXED_START_FLEXIBLE_DURATION', 'pax': {'min': 1, 'max': 9}, 'cashback': {'value': 10.0, 'type': 'PERCENTAGE'}, 'ticketDeliveryInfoHtml': '
You will receive an email confirmation with your Sagrada Familia mobile tickets within 30 minutes of your purchase. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.
\n
Please note that, in some rare cases, if the requested time slot is unavailable, we will book the next available time slot within 30 minutes of your preferred entry time. Please check the exact entry time on your tickets once you receive them.
', 'inputFields': [{'oldId': 42252, 'id': 'NAME', 'name': 'Full Name', 'dataType': 'STRING', 'validation': {'regex': '\\s*[^\\s]+\\s+[^\\s]+.*', 'minLength': 3, 'maxLength': 80, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42253, 'id': 'EMAIL', 'name': 'Email', 'dataType': 'STRING', 'validation': {'regex': "(?i)^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+(\\s*,\\s*((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+)*$", 'minLength': 5, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42254, 'id': 'PHONE', 'name': 'Phone', 'dataType': 'STRING', 'validation': {'regex': None, 'minLength': None, 'maxLength': 40, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 51544, 'id': 'CUSTOM_51544', 'name': 'Select Audioguide language', 'dataType': 'ENUM', 'validation': {'regex': None, 'minLength': None, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': ['English', 'Spanish', 'Catalan', 'Chinese', 'French', 'German', 'Italian', 'Japanese', 'Russian', 'Portuguese']}, 'level': 'PRIMARY_CUSTOMER'}]}, {'id': 18620, 'name': 'English Guided Tour', 'description': '- Get Fast Track Access to Sagrada Familia Basilica\r\n- Expert English Guide\r\n- Small group tour\r\n- Headphones', 'duration': 3600000, 'inventoryType': 'FIXED_START_FIXED_DURATION', 'pax': {'min': 1, 'max': 100}, 'cashback': {'value': 0.0, 'type': 'PERCENTAGE'}, 'ticketDeliveryInfoHtml': '', 'inputFields': [{'oldId': 58972, 'id': 'NAME', 'name': 'Full Name', 'dataType': 'STRING', 'validation': {'regex': '\\s*[^\\s]+\\s+[^\\s]+.*', 'minLength': 3, 'maxLength': 80, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 58973, 'id': 'EMAIL', 'name': 'Email', 'dataType': 'STRING', 'validation': {'regex': "(?i)^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+(\\s*,\\s*((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+)*$", 'minLength': 5, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 58974, 'id': 'PHONE', 'name': 'Phone', 'dataType': 'STRING', 'validation': {'regex': None, 'minLength': None, 'maxLength': 40, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}]}], 'pricing': {'type': 'PER_PERSON', 'currencyCode': 'EUR', 'minimumPrice': {'originalPrice': 17, 'finalPrice': 17}, 'bestDiscount': 0}}
```

Body:
I'm setting up a button that save API datas into my database and I'm trying to find a way to get the description out of every products but they are not included with the main url that get all products by city: (https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr), they are only included if we use this link which call a product by its id: (https://www.test-headout.com/api/public/v1/product/get/508).

So I was adviced to first hit the Listing API to get all products. Iterate over the list and get id and hit the product/get API to get the description.

This is what I've tried so far but I don't get how to do the iteration part:

My Django views:

def api_data(request):
    response = requests.get("https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr",
      headers={
        "Headout-Auth": HEADOUT_TEST_API_KEY
      }
    ).json()

    if (request.GET.get('mybtn')):
        for item in response['items']:  # for loop
            Product.objects.get_or_create(
                title=item['name'],
                destination=item['city']['name'],
                #description=response['contentListHtml'][0]['html'],
                link=item['canonicalUrl'],
                image=item['image']['url'],
            )

    return render(request, "form.html")

How can I do this?

Based on @ralf answer this is what I came out with
:

def api_data(request):
    if (request.GET.get('mybtn')):
        url_1 = requests.get("https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr",
          headers={
            "Headout-Auth": HEADOUT_TEST_API_KEY
          }
        )

        base_url_2 = requests.get("https://www.test-headout.com/api/public/v1/product/get/",
          headers={
            "Headout-Auth": HEADOUT_TEST_API_KEY
          }
        )

        resp_data = url_1.json()

        for item in resp_data['items']:
            url = '{}{}'.format(base_url_2, item['id'])
            resp_data = url.json()

            Product.objects.get_or_create(
                title=item['name'],
                destination=item['city']['name'],
                description=response['contentListHtml'][0]['html'],
                link=item['canonicalUrl'],
                image=item['image']['url']
            )

    return render(request, "form.html")

Body:
Have you tried to make a GET request for each item? This can be done inside the for-loop that you already have.

Maybe like this:

import requests

url_1 = 'https://www.test-headout.com/api/public/v1/product/listing/list-by/city?cityCode=BARCELONA&limit=5000&language=fr'
base_url_2 = 'https://www.test-headout.com/api/public/v1/product/get/'
resp_data = requests.get(url_1).json()

for item in resp_data['items']:
    url = '{}{}'.format(base_url_2, item['id'])
    resp_data = requests.get(url).json()

    print('item')
    print(item)
    print('resp_data')
    print(resp_data)

    break

    Product.objects.get_or_create(...)

Running the code for the first item (notice the break I put there for this test) I get the correct output:

item
{'id': '7324', 'name': 'Billets Accès Rapide pour la Sagrada Familia', 'url': '/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'canonicalUrl': 'https://www.test-headout.com/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'city': {'name': 'Barcelona', 'code': 'BARCELONA'}, 'image': {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/eaeefed3-c87b-4531-82c0-0089dc8d11ec-7324-barcelona-sagrada-familia-skyline-day-01.jpg'}, 'neibhourhood': 'Barcelona', 'primaryCategory': {'id': 327, 'name': 'Sagrada Familia', 'cityCode': 'BARCELONA', 'url': '/category/327'}, 'currency': {'code': 'EUR', 'currencyName': 'Euro', 'symbol': 'EUR', 'localSymbol': '€', 'precision': 2, 'currency': 'EUR'}, 'startGeolocation': {'latitude': 41.403629302978516, 'longitude': 2.1743557453155518}, 'ratingCumulative': {'avg': 4.7, 'count': 2496}, 'pricing': {'type': 'PER_PERSON', 'currencyCode': 'EUR', 'minimumPrice': {'originalPrice': 17, 'finalPrice': 17}, 'bestDiscount': 0}}
resp_data
{'id': 7324, 'name': 'Fast Track Tickets to Sagrada Familia', 'url': '/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'canonicalUrl': 'https://www.test-headout.com/tour/7324/spain/barcelona/fast-track-tickets-to-sagrada-familia', 'neighbourhood': 'La Sagrada Familia', 'city': {'name': 'Barcelona', 'code': 'BARCELONA'}, 'currency': {'code': 'EUR', 'name': 'Euro', 'symbol': 'EUR', 'localSymbol': '€', 'precision': 2}, 'displayTags': ['family friendly', 'sightseeing tours', 'day trips', 'attraction tickets', 'skip the line', 'city passes'], 'images': [{'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/eaeefed3-c87b-4531-82c0-0089dc8d11ec-7324-barcelona-sagrada-familia-skyline-day-01.jpg'}, {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/ee6912c4-1f4a-482b-be4e-5c035404da96-7324-barcelona-sagrada-familia-skyline-day-02.jpg'}, {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/d983bcd2-be68-4e0e-9066-82adb95f468c-7324-barcelona-sagrada-familia-skyline-day-03.jpg'}, {'url': '//cdn-imgix.headout.com/tour/13376/TOUR-IMAGE/e8afec3d-acad-4056-b15c-f63f8fda9a22-7324-barcelona-sagrada-familia-skyline-day-04.jpg'}], 'contentListHtml': [{'title': 'Summary', 'type': 'SUMMARY_HTML', 'html': '<h2>What to Expect</h2>\n<p>Purchasing tickets for entry at the venue is not only time consuming but often times, futile. Since each time slot for entry into the basilica has limited capacity, same day entry tickets on site tend to sell out before mid-day, even if you waited in line for several hours, as many visitors must do. Our fast track Sagrada Familia tickets provide you with priority access to this iconic basilica. Once you book your tickets, you will receive them via email which you can then display on your phone to skip the ticketing line entirely and proceed directly to the security check.</p>\n<p>Before you head into the inner sanctum of the church, make sure to explore the outer walls and facades of the Sagrada Familia. Two of the three facades, Nativity, and Passion, have been completed, while the third, the Glory Facade, is still under construction. The Nativity facade tells the story of the birth of Jesus Christ while the Passion Facade is dedicated to his death. Though you can admire the facades and towers from the outside, these tickets do not give you access to the towers themselves. If you wish to gain entry to the towers along with the Basilica, please click <a href="https://www.headout.com/tour/7603/spain/barcelona/sagrada-familia-skip-the-line-tickets-with-tower-access-and-audio-guide">here.</a></p>\n<p>As you move inside, you will come face to face with one of the most enchanting interiors of any church in the world. The vaults of the Basilica are as high as 70 meters in certain places and the beautiful, stained glass windows, constructed as per Gaudi’s plans, as well as the pillars holding up the ceiling,  make the entire space appear as though it were the hollowed out rib cage of some giant celestial beast. The magical natural light filtering through the basilica’s exquisite windows will take your breath away and transport you to a different time.</p>'}, {'title': 'Highlights', 'type': 'HIGHLIGHTS_HTML', 'html': '<ul>\n<li>Get a chance to visit the most iconic monument in Barcelona that has been under construction since 1882, this UNESCO World Heritage Site attracts more visitors each year than any other landmark in Spain.</li>\n<li>With these fast-track tickets that you can book online, you need not worry about the long winding queues and you will also be guaranteed access to an otherwise sold out attraction. </li>\n<li>Marvel at the magnificent inner sanctums of the basilica with its extraordinary vaulted ceilings and stained glass windows, bound to sweep you off your feet.</li>\n</ul>'}, {'title': 'FAQs', 'type': 'FAQ_HTML', 'html': '<h2>Know Before You Go</h2>\n<p><strong>Important Information</strong></p>\n<p>Groups with more than a total of 9 people, either adults or children, will not be allowed to gain entry into the Basilica. Should you wish to visit with a group of more than 9 people, you must book a guided visit as per the rules of the Basilica. You can book this by clicking <a href="https://www.headout.com/tour/3441/spain/barcelona/fast-track-guided-tour-of-sagrada-familia-without-tower-access">here.</a></p>\n<p><strong>Timings</strong></p>\n<p>Entry to the Sagrada Familia is timed. You will have the option to choose the time slot for your entry during the next step of your booking. Please note that you will <strong><em>only be allowed to gain entry at the time chosen during the booking</em></strong> so we recommend arriving 15 minutes in advance in order to ensure a hassle-free entry. Though your entry is timed, you may explore the basilica for as long as you wish. Most visitors spend around an hour and a half inside. </p>\n<p>November to February - 9:00 AM to 6:00 PM<br>\nMarch - 9:00 AM to 7:00 PM<br>\nApril to September - 9:00 AM to 8:00 PM<br>\nOctober - 9:00 AM to 7:00 PM<br>\nDecember 25, 26, January 1 and 6 - 9:00 AM to 2:00 PM<br></p>\n<p><strong>Reduced Price Tickets</strong></p>\n<p>Entry for children aged eleven and below is free, however, they will not receive an audioguide. You will have the option to choose the number of children who require free entry at the time of checkout.</p>\n<p><strong>Getting There</strong></p>\n<p>To get to the Sagrada Familia, you can take any of the following options:<br>\nBy Metro: Line 2 and Line 5 to the Sagrada Familia Station.<br>\nBy Bus: Buses 19, 33, 34, 43, 44, 50, 51, B20 and B24 will stop at the Sagrada Familia.<br></p>\n<p>The main access to the Sagrada Familia is on the Carrer de la Marina (C/Marina).</p>\n<p><strong>Dress Code</strong></p>\n<p>To pay your respects to the Holy family, you must be appropriately covered to enter this sacred monument. No tank tops, strapless shirts, short shorts or sandals will be accepted. You will be denied entry if this dress code is not adhered to.</p>\n<p><strong>Accessibility</strong></p>\n<p>The insides of the Sagrada Familia is wheelchair accessible. \nHowever, due to the narrow stairways leading to the towers, tower access is not suitable for individuals with mobility impairments.</p>\n<p>Only small backpacks and regular-size handbags are allowed inside. Guests with larger bags will not be allowed to enter (as there are no storage facilities).</p>'}, {'title': 'Cancellation Policy', 'type': 'CANCELLATION_HTML', 'html': '<h2>Strict Cancellation Policy</h2>\n<p>These tickets cannot be cancelled, amended or rescheduled.</p>'}, {'title': 'Ticket Delivery Information', 'type': 'TICKET_DELIVERY_INFO_HTML', 'html': '<p>You will receive an email confirmation with your Sagrada Familia mobile tickets instantly. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.</p>'}, {'title': 'Inclusions', 'type': 'INCLUSIONS_HTML', 'html': '<ul>\n<li>Skip-the-Line access to the Sagrada Familia.</li>\n</ul>'}, {'title': 'Exclusions', 'type': 'EXCLUSIONS_HTML', 'html': '<ul>\n<li>Tower access.</li>\n</ul>'}], 'content': [{'title': 'Summary', 'type': 'SUMMARY_HTML', 'html': '<h2>What to Expect</h2>\n<p>Purchasing tickets for entry at the venue is not only time consuming but often times, futile. Since each time slot for entry into the basilica has limited capacity, same day entry tickets on site tend to sell out before mid-day, even if you waited in line for several hours, as many visitors must do. Our fast track Sagrada Familia tickets provide you with priority access to this iconic basilica. Once you book your tickets, you will receive them via email which you can then display on your phone to skip the ticketing line entirely and proceed directly to the security check.</p>\n<p>Before you head into the inner sanctum of the church, make sure to explore the outer walls and facades of the Sagrada Familia. Two of the three facades, Nativity, and Passion, have been completed, while the third, the Glory Facade, is still under construction. The Nativity facade tells the story of the birth of Jesus Christ while the Passion Facade is dedicated to his death. Though you can admire the facades and towers from the outside, these tickets do not give you access to the towers themselves. If you wish to gain entry to the towers along with the Basilica, please click <a href="https://www.headout.com/tour/7603/spain/barcelona/sagrada-familia-skip-the-line-tickets-with-tower-access-and-audio-guide">here.</a></p>\n<p>As you move inside, you will come face to face with one of the most enchanting interiors of any church in the world. The vaults of the Basilica are as high as 70 meters in certain places and the beautiful, stained glass windows, constructed as per Gaudi’s plans, as well as the pillars holding up the ceiling,  make the entire space appear as though it were the hollowed out rib cage of some giant celestial beast. The magical natural light filtering through the basilica’s exquisite windows will take your breath away and transport you to a different time.</p>'}, {'title': 'Highlights', 'type': 'HIGHLIGHTS_HTML', 'html': '<ul>\n<li>Get a chance to visit the most iconic monument in Barcelona that has been under construction since 1882, this UNESCO World Heritage Site attracts more visitors each year than any other landmark in Spain.</li>\n<li>With these fast-track tickets that you can book online, you need not worry about the long winding queues and you will also be guaranteed access to an otherwise sold out attraction. </li>\n<li>Marvel at the magnificent inner sanctums of the basilica with its extraordinary vaulted ceilings and stained glass windows, bound to sweep you off your feet.</li>\n</ul>'}, {'title': 'FAQs', 'type': 'FAQ_HTML', 'html': '<h2>Know Before You Go</h2>\n<p><strong>Important Information</strong></p>\n<p>Groups with more than a total of 9 people, either adults or children, will not be allowed to gain entry into the Basilica. Should you wish to visit with a group of more than 9 people, you must book a guided visit as per the rules of the Basilica. You can book this by clicking <a href="https://www.headout.com/tour/3441/spain/barcelona/fast-track-guided-tour-of-sagrada-familia-without-tower-access">here.</a></p>\n<p><strong>Timings</strong></p>\n<p>Entry to the Sagrada Familia is timed. You will have the option to choose the time slot for your entry during the next step of your booking. Please note that you will <strong><em>only be allowed to gain entry at the time chosen during the booking</em></strong> so we recommend arriving 15 minutes in advance in order to ensure a hassle-free entry. Though your entry is timed, you may explore the basilica for as long as you wish. Most visitors spend around an hour and a half inside. </p>\n<p>November to February - 9:00 AM to 6:00 PM<br>\nMarch - 9:00 AM to 7:00 PM<br>\nApril to September - 9:00 AM to 8:00 PM<br>\nOctober - 9:00 AM to 7:00 PM<br>\nDecember 25, 26, January 1 and 6 - 9:00 AM to 2:00 PM<br></p>\n<p><strong>Reduced Price Tickets</strong></p>\n<p>Entry for children aged eleven and below is free, however, they will not receive an audioguide. You will have the option to choose the number of children who require free entry at the time of checkout.</p>\n<p><strong>Getting There</strong></p>\n<p>To get to the Sagrada Familia, you can take any of the following options:<br>\nBy Metro: Line 2 and Line 5 to the Sagrada Familia Station.<br>\nBy Bus: Buses 19, 33, 34, 43, 44, 50, 51, B20 and B24 will stop at the Sagrada Familia.<br></p>\n<p>The main access to the Sagrada Familia is on the Carrer de la Marina (C/Marina).</p>\n<p><strong>Dress Code</strong></p>\n<p>To pay your respects to the Holy family, you must be appropriately covered to enter this sacred monument. No tank tops, strapless shirts, short shorts or sandals will be accepted. You will be denied entry if this dress code is not adhered to.</p>\n<p><strong>Accessibility</strong></p>\n<p>The insides of the Sagrada Familia is wheelchair accessible. \nHowever, due to the narrow stairways leading to the towers, tower access is not suitable for individuals with mobility impairments.</p>\n<p>Only small backpacks and regular-size handbags are allowed inside. Guests with larger bags will not be allowed to enter (as there are no storage facilities).</p>'}, {'title': 'Cancellation Policy', 'type': 'CANCELLATION_HTML', 'html': '<h2>Strict Cancellation Policy</h2>\n<p>These tickets cannot be cancelled, amended or rescheduled.</p>'}, {'title': 'Ticket Delivery Information', 'type': 'TICKET_DELIVERY_INFO_HTML', 'html': '<p>You will receive an email confirmation with your Sagrada Familia mobile tickets instantly. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.</p>'}, {'title': 'Inclusions', 'type': 'INCLUSIONS_HTML', 'html': '<ul>\n<li>Skip-the-Line access to the Sagrada Familia.</li>\n</ul>'}, {'title': 'Exclusions', 'type': 'EXCLUSIONS_HTML', 'html': '<ul>\n<li>Tower access.</li>\n</ul>'}], 'startLocation': {'geolocation': {'latitude': 41.403629302978516, 'longitude': 2.1743557453155518}, 'address': {'addressLine1': 'Sagrada Família, Carrer de la marina', 'addressLine2': None, 'cityName': 'Barcelona', 'postalCode': '08013', 'state': 'Catalunya', 'countryName': 'Spain'}}, 'endLocation': {'geolocation': {'latitude': 41.403629302978516, 'longitude': 2.1743557453155518}, 'address': {'addressLine1': 'Sagrada Família, Carrer de la marina', 'addressLine2': None, 'cityName': 'Barcelona', 'postalCode': '08013', 'state': 'Catalunya', 'countryName': 'Spain'}}, 'productType': 'ATTRACTION', 'ratingCumulative': {'avg': 4.7, 'count': 2496}, 'hasInstantConfirmation': True, 'hasMobileTicket': True, 'variants': [{'id': 13376, 'name': 'Fast Track', 'description': '- Get Fast Track Access that lets you bypass the ticket line & directly join the security line\r\n- Get guaranteed timed entry to the basilica\r\n- Mobile vouchers for convenient access', 'duration': 5400000, 'inventoryType': 'FIXED_START_FLEXIBLE_DURATION', 'pax': {'min': 1, 'max': 9}, 'cashback': {'value': 10.0, 'type': 'PERCENTAGE'}, 'ticketDeliveryInfoHtml': '<p>You will receive an email confirmation with your Sagrada Familia mobile tickets within 30 minutes of your purchase. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.</p>\n<p>Please note that, in some rare cases, if the requested time slot is unavailable, we will book the next available time slot within 30 minutes of your preferred entry time. Please check the exact entry time on your tickets once you receive them.</p>', 'inputFields': [{'oldId': 42249, 'id': 'NAME', 'name': 'Full Name', 'dataType': 'STRING', 'validation': {'regex': '\\s*[^\\s]+\\s+[^\\s]+.*', 'minLength': 3, 'maxLength': 80, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42250, 'id': 'EMAIL', 'name': 'Email', 'dataType': 'STRING', 'validation': {'regex': "(?i)^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+(\\s*,\\s*((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+)*$", 'minLength': 5, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42251, 'id': 'PHONE', 'name': 'Phone', 'dataType': 'STRING', 'validation': {'regex': None, 'minLength': None, 'maxLength': 40, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}]}, {'id': 13377, 'name': 'Fast Track + Audioguide', 'description': '- Audioguide (Spanish, Catalan, Chinese, English, French, German, Italian, Japanese, Portuguese, Russian)\r\n- Get Fast Track Access that lets you bypass the ticket line & directly join the security line\r\n- Get guaranteed timed entry to the basilica\r\n- Mobile vouchers for convenient access', 'duration': 5400000, 'inventoryType': 'FIXED_START_FLEXIBLE_DURATION', 'pax': {'min': 1, 'max': 9}, 'cashback': {'value': 10.0, 'type': 'PERCENTAGE'}, 'ticketDeliveryInfoHtml': '<p>You will receive an email confirmation with your Sagrada Familia mobile tickets within 30 minutes of your purchase. No need to print. You can skip the ticket line and go directly to the security line with your mobile ticket.</p>\n<p>Please note that, in some rare cases, if the requested time slot is unavailable, we will book the next available time slot within 30 minutes of your preferred entry time. Please check the exact entry time on your tickets once you receive them.</p>', 'inputFields': [{'oldId': 42252, 'id': 'NAME', 'name': 'Full Name', 'dataType': 'STRING', 'validation': {'regex': '\\s*[^\\s]+\\s+[^\\s]+.*', 'minLength': 3, 'maxLength': 80, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42253, 'id': 'EMAIL', 'name': 'Email', 'dataType': 'STRING', 'validation': {'regex': "(?i)^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+(\\s*,\\s*((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+)*$", 'minLength': 5, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 42254, 'id': 'PHONE', 'name': 'Phone', 'dataType': 'STRING', 'validation': {'regex': None, 'minLength': None, 'maxLength': 40, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 51544, 'id': 'CUSTOM_51544', 'name': 'Select Audioguide language', 'dataType': 'ENUM', 'validation': {'regex': None, 'minLength': None, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': ['English', 'Spanish', 'Catalan', 'Chinese', 'French', 'German', 'Italian', 'Japanese', 'Russian', 'Portuguese']}, 'level': 'PRIMARY_CUSTOMER'}]}, {'id': 18620, 'name': 'English Guided Tour', 'description': '- Get Fast Track Access to Sagrada Familia Basilica\r\n- Expert English Guide\r\n- Small group tour\r\n- Headphones', 'duration': 3600000, 'inventoryType': 'FIXED_START_FIXED_DURATION', 'pax': {'min': 1, 'max': 100}, 'cashback': {'value': 0.0, 'type': 'PERCENTAGE'}, 'ticketDeliveryInfoHtml': '', 'inputFields': [{'oldId': 58972, 'id': 'NAME', 'name': 'Full Name', 'dataType': 'STRING', 'validation': {'regex': '\\s*[^\\s]+\\s+[^\\s]+.*', 'minLength': 3, 'maxLength': 80, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 58973, 'id': 'EMAIL', 'name': 'Email', 'dataType': 'STRING', 'validation': {'regex': "(?i)^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+(\\s*,\\s*((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))+)*$", 'minLength': 5, 'maxLength': None, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}, {'oldId': 58974, 'id': 'PHONE', 'name': 'Phone', 'dataType': 'STRING', 'validation': {'regex': None, 'minLength': None, 'maxLength': 40, 'minValue': None, 'maxValue': None, 'required': True, 'values': None}, 'level': 'PRIMARY_CUSTOMER'}]}], 'pricing': {'type': 'PER_PERSON', 'currencyCode': 'EUR', 'minimumPrice': {'originalPrice': 17, 'finalPrice': 17}, 'bestDiscount': 0}}


来源: stackexchange

问:
Get a single compare function to order both ASC and DESC using Array.sort()?
I need to order an array of objects by the property price.

Right now I have the following, which works as intended:

```<code>function orderByPriceASC(a,b) {
    return(
      a.price < b.price ? -1
      : a.price > b.price ? 1
        : 0
    );
  }

  function orderByPriceDESC(a,b) {
    return(
      a.price < b.price ? 1
      : a.price > b.price ? -1
        : 0
    );
  }

  function sortByPrice(order) {
    setProductList((prevState) => {
      const aux = Array.from(prevState);
      order === 'asc' ? aux.sort(orderByPriceASC) : aux.sort(orderByPriceDESC);
      return aux;
    });
  }
</code>```

But can is there a way that I can structure this so I can get a single compare function that works for both ASC and DESC order?

Something like:

```<code>function orderByPrice(a,b,order) {
    return(
      a.price < b.price ?
        order === 'asc' ? -1 : 1
      : a.price > b.price ? 1
        order === 'asc' ? 1 : -1
        : 0
    );
  }
</code>```

The problem is that I would have to send that extra parameter down to the Array.sort method which I don't think it's possible. Maybe with some wrapper function.

How can this be implemented?

答:
You can do something like this with Array.sort and Array.slice:

let arr = [{ price: 2 },{ price: 1 },{ price: 3 }]

let sortBy = (arr, prop, dir=null) =>
  arr.slice().sort((a,b) => (a[prop] - b[prop]) * (dir ? -1 : 1))

console.log(sortBy(arr, 'price'))
console.log(sortBy(arr, 'price', 'desc')) // <-- any truthy value for `desc````
</div>
</div>
</p>

<p>You have to use Array.slice to clone the array since Array.sort does mutate the array and you want a <code>pure</code> function. From the documentation:</p>

<blockquote>
  <p>The sorted array. Note that the array is sorted in place, and no copy
  is made.</p>
</blockquote>

<p>In addition if you are using lodash there is a _.orderBy function for that already which also allows sorting on multiple props etc:</p>

<p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
<div class="snippet-code">
<pre class="snippet-code-js lang-js prettyprint-override"><code>let arr = [{ price: 2 },{ price: 1 },{ price: 3 }]

console.log(_.orderBy(arr, 'price'))
console.log(_.orderBy(arr, 'price', 'desc'))</code>```

```

ContentLicense: CC BY-SA 4.0

Body:
I need to order an array of objects by the property price.

Right now I have the following, which works as intended:

function orderByPriceASC(a,b) {
    return(
      a.price  b.price ? 1
        : 0
    );
  }

  function orderByPriceDESC(a,b) {
    return(
      a.price  b.price ? -1
        : 0
    );
  }

  function sortByPrice(order) {
    setProductList((prevState) => {
      const aux = Array.from(prevState);
      order === 'asc' ? aux.sort(orderByPriceASC) : aux.sort(orderByPriceDESC);
      return aux;
    });
  }

But can is there a way that I can structure this so I can get a single compare function that works for both ASC and DESC order?

Something like:

function orderByPrice(a,b,order) {
    return(
      a.price  b.price ? 1
        order === 'asc' ? 1 : -1
        : 0
    );
  }

The problem is that I would have to send that extra parameter down to the Array.sort method which I don't think it's possible. Maybe with some wrapper function.

How can this be implemented?

Body:
You can do something like this with Array.sort and Array.slice:

let arr = [{ price: 2 },{ price: 1 },{ price: 3 }]

let sortBy = (arr, prop, dir=null) =>
  arr.slice().sort((a,b) => (a[prop] - b[prop]) * (dir ? -1 : 1))

console.log(sortBy(arr, 'price'))
console.log(sortBy(arr, 'price', 'desc')) // <-- any truthy value for `desc`

You have to use Array.slice to clone the array since Array.sort does mutate the array and you want a pure function. From the documentation:

The sorted array. Note that the array is sorted in place, and no copy
  is made.

In addition if you are using lodash there is a _.orderBy function for that already which also allows sorting on multiple props etc:

let arr = [{ price: 2 },{ price: 1 },{ price: 3 }]

console.log(_.orderBy(arr, 'price'))
console.log(_.orderBy(arr, 'price', 'desc'))

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>


问:
Websocket transport reliability (Socket.io data loss during reconnection)

Used

NodeJS, Socket.io

Problem

Imagine there are 2 users U1 & U2, connected to an app via Socket.io. The algorithm is the following:

U1 completely loses Internet connection (ex. switches Internet off)

U2 sends a message to U1.

U1 does not receive the message yet, because the Internet is down

Server detects U1 disconnection by heartbeat timeout

U1 reconnects to socket.io

U1 never receives the message from U2 - it is lost on Step 4 I guess.

Possible explanation

I think I understand why it happens:

on Step 4 Server kills socket instance and the queue of messages to U1 as well

Moreover on Step 5 U1 and Server create new connection (it is not reused), so even if message is still queued, the previous connection is lost anyway.

Need help

How can I prevent this kind of data loss? I have to use hearbeats, because I do not people hang in app forever. Also I must still give a possibility to reconnect, because when I deploy a new version of app I want zero downtime.

P.S. The thing I call "message" is not just a text message I can store in database, but valuable system message, which delivery must be guaranteed, or UI screws up.

Thanks!

Addition 1

I do already have a user account system. Moreover, my application is already complex. Adding offline/online statuses won't help, because I already have this kind of stuff. The problem is different.

Check out step 2. On this step we technically cannot say if U1 goes offline, he just loses connection lets say for 2 seconds, probably because of bad internet. So U2 sends him a message, but U1 doesn't receive it because internet is still down for him (step 3). Step 4 is needed to detect offline users, lets say, the timeout is 60 seconds. Eventually in another 10 seconds internet connection for U1 is up and he reconnects to socket.io. But the message from U2 is lost in space because on server U1 was disconnected by timeout.

That is the problem, I wan't 100% delivery.

Solution

Collect an emit (emit name and data) in {} user, identified by random emitID. Send emit

Confirm the emit on client side (send emit back to server with emitID)

If confirmed - delete object from {} identified by emitID

If user reconnected - check {} for this user and loop through it executing Step 1 for each object in {}

When disconnected or/and connected flush {} for user if necessary

```<code>// Server
const pendingEmits = {};

socket.on('reconnection', () => resendAllPendingLimits);
socket.on('confirm', (emitID) => { delete(pendingEmits[emitID]); });

// Client
socket.on('something', () => {
    socket.emit('confirm', emitID);
});
</code>```

Solution 2 (kinda)

Added 1 Feb 2020.

While this is not really a solution for Websockets, someone may still find it handy. We migrated from Websockets to SSE + Ajax. SSE allows you to connect from a client to keep a persistent TCP connection and receive messages from a server in realtime. To send messages from a client to a server - simply use Ajax. There are disadvantages like latency and overhead, but SSE guarantees reliability because it is a TCP connection.

Since we use Express we use this library for SSE https://github.com/dpskvn/express-sse, but you can choose the one that fits you.

SSE is not supported in IE and most Edge versions, so you would need a polyfill: https://github.com/Yaffle/EventSource.

答:
Michelle's answer is pretty much on point, but there are a few other important things to consider.  The main question to ask yourself is: "Is there a difference between a user and a socket in my app?" Another way to ask that is "Can each logged in user have more than 1 socket connection at one time?"

In the web world it is probably always a possibility that a single user has multiple socket connections, unless you have specifically put something in place that prevents this.  The simplest example of this is if a user has two tabs of the same page open.  In these cases you don't care about sending a message/event to the human user just once... you need to send it to each socket instance for that user so that each tab can run it's callbacks to update the ui state.  Maybe this isn't a concern for certain applications, but my gut says it would be for most.  If this is a concern for you, read on....

To solve this (assuming you are using a database as your persistent storage) you would need 3 tables.

users - which is a 1 to 1 with real people

clients - which represents a "tab" that could have a single connection to a socket server.  (any 'user' may have multiple)

messages - a message that needs sent to a client (not a message that needs sent to a user or to a socket)

The users table is optional if your app doesn't require it, but the OP said they have one.

The other thing that needs properly defined is "what is a socket connection?", "When is a socket connection created?", "when is a socket connection reused?".  Michelle's psudocode makes it seem like a socket connection can be reused.  With Socket.IO, they CANNOT be reused.  I've seen be the source of a lot of confusion.  There are real life scenarios where Michelle's example does make sense. But I have to imagine those scenarios are rare.  What really happens is when a socket connection is lost, that connection, ID, etc will never be reused.  So any messages marked for that socket specifically will never be delivered to anyone because when the client who had originally connected, reconnects, they get a completely brand new connection and new ID.  This means it's up to you to do something to track clients (rather than sockets or users) across multiple socket connections.

So for a web based example here would be the set of steps I'd recommend:

When a user loads a client (typically a single webpage) that has the potential for creating a socket connection, add a row to the clients database which is linked to their user ID.

When the user actually does connect to the socket server, pass the client ID to the server with the connection request.

The server should validate the user is allowed to connect and the client row in the clients table is available for connection and allow/deny accordingly.

Update the client row with the socket ID generated by Socket.IO.

Send any items in the messages table connected to the client ID.  There wouldn't be any on initial connection, but if this was from the client trying to reconnect, there may be some.

Any time a message needs to be sent to that socket, add a row in the messages table which is linked to the client ID you generated (not the socket ID).

Attempt to emit the message and listen for the client with the acknowledgement.

When you get the acknowledgement, delete that item from the messages table.

You may wish to create some logic on the client side that discards duplicate messages sent from the server since this is technically a possibility as some have pointed out.

Then when a client disconnects from the socket server (purposefully or via error), DO NOT delete the client row, just clear out the socket ID at most.  This is because that same client could try to reconnect.

When a client tries to reconnect, send the same client ID it sent with the original connection attempt.  The server will view this just like an initial connection.

When the client is destroyed (user closes the tab or navigates away), this is when you delete the client row and all messages for this client.  This step may be a bit tricky.

Because the last step is tricky (at least it used to be, I haven't done anything like that in a long time), and because there are cases like power loss where the client will disconnect without cleaning up the client row and never tries to reconnect with that same client row - you probably want to have something that runs periodically to cleanup any stale client and message rows.  Or, you can just permanently store all clients and messages forever and just mark their state appropriately.

So just to be clear, in cases where one user has two tabs open, you will be adding two identical message to the messages table each marked for a different client because your server needs to know if each client received them, not just each user.

Body:
Used

NodeJS, Socket.io

Problem

Imagine there are 2 users U1 & U2, connected to an app via Socket.io. The algorithm is the following:

U1 completely loses Internet connection (ex. switches Internet off)

U2 sends a message to U1.

U1 does not receive the message yet, because the Internet is down

Server detects U1 disconnection by heartbeat timeout

U1 reconnects to socket.io

U1 never receives the message from U2 - it is lost on Step 4 I guess.

Possible explanation

I think I understand why it happens:

on Step 4 Server kills socket instance and the queue of messages to U1 as well

Moreover on Step 5 U1 and Server create new connection (it is not reused), so even if message is still queued, the previous connection is lost anyway.

Need help

How can I prevent this kind of data loss? I have to use hearbeats, because I do not people hang in app forever. Also I must still give a possibility to reconnect, because when I deploy a new version of app I want zero downtime.

P.S. The thing I call "message" is not just a text message I can store in database, but valuable system message, which delivery must be guaranteed, or UI screws up.

Thanks!

Addition 1

I do already have a user account system. Moreover, my application is already complex. Adding offline/online statuses won't help, because I already have this kind of stuff. The problem is different.

Check out step 2. On this step we technically cannot say if U1 goes offline, he just loses connection lets say for 2 seconds, probably because of bad internet. So U2 sends him a message, but U1 doesn't receive it because internet is still down for him (step 3). Step 4 is needed to detect offline users, lets say, the timeout is 60 seconds. Eventually in another 10 seconds internet connection for U1 is up and he reconnects to socket.io. But the message from U2 is lost in space because on server U1 was disconnected by timeout.

That is the problem, I wan't 100% delivery.

Solution

Collect an emit (emit name and data) in {} user, identified by random emitID. Send emit

Confirm the emit on client side (send emit back to server with emitID)

If confirmed - delete object from {} identified by emitID

If user reconnected - check {} for this user and loop through it executing Step 1 for each object in {}

When disconnected or/and connected flush {} for user if necessary

// Server
const pendingEmits = {};

socket.on('reconnection', () => resendAllPendingLimits);
socket.on('confirm', (emitID) => { delete(pendingEmits[emitID]); });

// Client
socket.on('something', () => {
    socket.emit('confirm', emitID);
});

Solution 2 (kinda)

Added 1 Feb 2020.

While this is not really a solution for Websockets, someone may still find it handy. We migrated from Websockets to SSE + Ajax. SSE allows you to connect from a client to keep a persistent TCP connection and receive messages from a server in realtime. To send messages from a client to a server - simply use Ajax. There are disadvantages like latency and overhead, but SSE guarantees reliability because it is a TCP connection.

Since we use Express we use this library for SSE https://github.com/dpskvn/express-sse, but you can choose the one that fits you.

SSE is not supported in IE and most Edge versions, so you would need a polyfill: https://github.com/Yaffle/EventSource.


问:
Why am I getting error at line 1 on Create Table
First time working with MySQL. Using MySQL Workbench with a new instance installed on my Windows 10 desktop. I've created a test test db/schema, and now trying to create a table.

```<code>CREATE TABLE kcplusers (
'user_barcode' varchar(255),
'first_name' varchar(255),
'middle_name' varchar(255),
'last_name' varchar(255),
'address_street' varchar(500),
'city_state' varchar(500),
'zip' varchar(100),
'user_profile' varchar(500),
'birth_date' DATE
);
</code>```

Workbench shows a red x at line 1 and red slashes under the open-parenth (

I get the error:

Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''user_barcode' varchar(255),  'first_name' varchar(255),      'middle_name' varc' at line 2

What am I missing?

答:
You're using apostrophes around the column names - they should be removed in this case.

If you must specify certain characters in a column name you can use ascii cd 96 shown as ` - usually just to the left of the number "1" on your keyboard.

But to keep it simple as you're starting out - just remove the apostrophes.
And let us know how it turns out (good or bad)
Cheers!

Body:
First time working with MySQL. Using MySQL Workbench with a new instance installed on my Windows 10 desktop. I've created a test test db/schema, and now trying to create a table.

CREATE TABLE kcplusers (
'user_barcode' varchar(255),
'first_name' varchar(255),
'middle_name' varchar(255),
'last_name' varchar(255),
'address_street' varchar(500),
'city_state' varchar(500),
'zip' varchar(100),
'user_profile' varchar(500),
'birth_date' DATE
);

Workbench shows a red x at line 1 and red slashes under the open-parenth (

I get the error:

Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''user_barcode' varchar(255),  'first_name' varchar(255),      'middle_name' varc' at line 2

What am I missing?


问:
Expanded Tree Cache Full Exception while executing XQuery on a huge number of documents
I am trying to retrieve value of XPath from multiple documents. In my case approximately 800k documents. I am getting and exception called

Expanded Tree cache full.

The same query yields the expected output when there are less documents i.e for 400k documents.

Following is the query used to retrieve value of XPath from collections: collection1 and collection2:

```<code>cts:search(doc(),
  cts:and-query((
    cts:collection-query("collection1"),
    cts:collection-query("collection2")
  ))
)/xpath/text()
</code>```

答:
Each document you return from a query must be loaded into memory in full - that is the expanded tree cache. Therefore, a single query is limited by the number of documents it can fit into memory.

For queries that need to read the entire database - or a large subset - you should implement paging or batching to prevent the error. If you are trying to export documents from the database MLCP will do the batching for you automatically.

Body:
I am trying to retrieve value of XPath from multiple documents. In my case approximately 800k documents. I am getting and exception called

Expanded Tree cache full.

The same query yields the expected output when there are less documents i.e for 400k documents.

Following is the query used to retrieve value of XPath from collections: collection1 and collection2:

cts:search(doc(),
  cts:and-query((
    cts:collection-query("collection1"),
    cts:collection-query("collection2")
  ))
)/xpath/text()


问:
How do I set a crontab from a Perl script?
I'm currently writing a CGI script in Perl that is supposed to read in a crontab (works), allows for easy manipulation in a web browser (TODO) and should finally set the crontab when editing it is done (fails).
To preemtively answer any questions on security: The script is reachable only within a VPN tunnel and so is invisible on the 'Net...

Anyway, here's the code in question:

```<code>if($p_action eq 'set')
  {
my $l_i = 1;
my $l_param;

  if(!open(CRON, "| /usr/bin/sudo /usr/bin/crontab"))
    {
    print CRON "CRON_TZ=$l_param\n" if(defined ($l_param = $cgi->param("cron_tz")));
    print CRON "MAILTO=$l_param\n" if(defined ($l_param = $cgi->param("mailto")));
    print CRON "SHELL=$l_param\n" if(($l_param = $cgi->param("shell")) ne 'unset');

    print CRON "\n" if(defined $cgi->param("cron_tz") || defined $cgi->param("mailto") || $cgi->param('shell') ne 'unset');

    while(defined $cgi->param("line$l_i-min"))
      {
my $l_min = $cgi->param("line$l_i-min");
my $l_hour = $cgi->param("line$l_i-hour");
my $l_day = $cgi->param("line$l_i-day");
my $l_month = $cgi->param("line$l_i-month");
my $l_wday = $cgi->param("line$l_i-wday");
my $l_cmd = $cgi->param("line$l_i-cmd");

      print CRON "- " if($cgi->param("line$l_i-nolog") eq 'Nolog');
      print CRON $l_min.' ';
      print CRON $l_hour.' ';
      print CRON $l_day.' ';
      print CRON $l_month.' ';
      print CRON $l_wday.' ';
      print CRON $l_cmd."\n";
      $l_i++;
      }

    close(CRON);
    }
  }
</code>```

I already have permitted both /usr/bin/crontab -l and /usr/bin/crontab in the sudoers file for execution by wwwrun.

Although crontab is actually executed when opening the pipe, nothing, however, is passed to crontab and so resulting in an empty crontab. It appears that I'm missing something crucial here, but I don't possibly see what it could be. Any clues on how to get this thing unstuck?

P.S.: I want to confine root privileges to as few lines of code as possible so running the script as root is not an option.

答:
sudo consumes standard input in case a password is supplied, so it is not possible to pipe input to a sudo'ed command.

To install a crontab, crontab supports the syntax

```<code>crontab FILE
</code>```

So as a workaround you could write the crontab contents to a file and call

```<code>system("/usr/bin/sudo /usr/bin/crontab $FILE");
</code>```

ContentLicense: CC BY-SA 3.0

Body:
I'm currently writing a CGI script in Perl that is supposed to read in a crontab (works), allows for easy manipulation in a web browser (TODO) and should finally set the crontab when editing it is done (fails).
To preemtively answer any questions on security: The script is reachable only within a VPN tunnel and so is invisible on the 'Net...

Anyway, here's the code in question:

if($p_action eq 'set')
  {
my $l_i = 1;
my $l_param;

  if(!open(CRON, "| /usr/bin/sudo /usr/bin/crontab"))
    {
    print CRON "CRON_TZ=$l_param\n" if(defined ($l_param = $cgi->param("cron_tz")));
    print CRON "MAILTO=$l_param\n" if(defined ($l_param = $cgi->param("mailto")));
    print CRON "SHELL=$l_param\n" if(($l_param = $cgi->param("shell")) ne 'unset');

    print CRON "\n" if(defined $cgi->param("cron_tz") || defined $cgi->param("mailto") || $cgi->param('shell') ne 'unset');

    while(defined $cgi->param("line$l_i-min"))
      {
my $l_min = $cgi->param("line$l_i-min");
my $l_hour = $cgi->param("line$l_i-hour");
my $l_day = $cgi->param("line$l_i-day");
my $l_month = $cgi->param("line$l_i-month");
my $l_wday = $cgi->param("line$l_i-wday");
my $l_cmd = $cgi->param("line$l_i-cmd");

      print CRON "- " if($cgi->param("line$l_i-nolog") eq 'Nolog');
      print CRON $l_min.' ';
      print CRON $l_hour.' ';
      print CRON $l_day.' ';
      print CRON $l_month.' ';
      print CRON $l_wday.' ';
      print CRON $l_cmd."\n";
      $l_i++;
      }

    close(CRON);
    }
  }

I already have permitted both /usr/bin/crontab -l and /usr/bin/crontab in the sudoers file for execution by wwwrun.

Although crontab is actually executed when opening the pipe, nothing, however, is passed to crontab and so resulting in an empty crontab. It appears that I'm missing something crucial here, but I don't possibly see what it could be. Any clues on how to get this thing unstuck?

P.S.: I want to confine root privileges to as few lines of code as possible so running the script as root is not an option.

Body:
sudo consumes standard input in case a password is supplied, so it is not possible to pipe input to a sudo'ed command.

To install a crontab, crontab supports the syntax

crontab FILE

So as a workaround you could write the crontab contents to a file and call

system("/usr/bin/sudo /usr/bin/crontab $FILE");


问:
React web scraping without using a server
There is a way in React to do web scraping without using a server that takes care of this.

I would like to use only React.

I was trying to use casperjs but it seems to give me problems on react when I set it, it can't find the module.

答: you have to use a server in order to do scraping in react. if you are using python or any other language you can do it via console.</p>


问:
How do I post contents of a Quill editor in a form?
I have what I think is a very common scenario. I would normally have this form:

```<code><form method="post">

<textarea name="text"></textarea>
<input type="submit" value="Save" />

</form>
</code>```

Then with PHP I would capture the data from the form ($_POST['text']) and I could use that in another variable.

Now, I'd like to use Quill so users have a nice rich text editor instead. Quill seems very well suited for this and the documentation is very detailed. However, for some reason I can not find how I can "post" the data to the form. There is one single sample page that sort of does what I want, but I am unable to fully implement this in my sample, and in the quick start guide this rather fundamental (to me) topic is not discussed, and I can not find this in the documentation either.

Is Quill supposed to be used like this? Am I overseeing something? Is there a recommended way to make this work?

This is what I currently have:

```<code><!doctype html>
<html>
<head>
<title>Test</title>
<meta charset="UTF-8" />
<link href="https://cdn.quilljs.com/1.0.0/quill.snow.css" rel="stylesheet">
</head>
<body>
<form method="post">

<!-- Create the toolbar container -->
<div id="toolbar">
  <button class="ql-bold">Bold</button>
  <button class="ql-italic">Italic</button>
</div>

<form method="post">

<!-- Create the editor container -->
<div id="editor">
  <p>Hello World!</p>
</div>

<input type="submit" value="Save" />

</form>

<!-- Include the Quill library -->
<script src="https://cdn.quilljs.com/1.0.0/quill.js"></script>

<!-- Initialize Quill editor -->
<script>
  var editor = new Quill('#editor', {
    modules: { toolbar: '#toolbar' },
    theme: 'snow'
  });
</script>
</body>
</html>
</code>```

答:
A solution I came up with was to make a wrapper class.

class QuillWrapper {

    /**
     * @param targetDivId { string } The id of the div the editor will be in.
     * @param targetInputId { string } The id of the hidden input
     * @param quillOptions { any } The options for quill
     */
    constructor(targetDivId, targetInputId, quillOptions) {

        //Validate target div existence
        this.targetDiv = document.getElementById(targetDivId);
        if (!this.targetDiv) throw "Target div id was invalid";

        //Validate target input existence
        this.targetInput = document.getElementById(targetInputId);
        if (!this.targetInput) throw "Target input id was invalid";

        //Init Quill
        this.quill = new Quill("#" + targetDivId, quillOptions);

        //Bind the two containers together by listening to the on-change event
        this.quill.on('text-change',
            () => {
                this.targetInput.value = this.targetDiv.children[0].innerHTML;
            });
    }
}
```

<p>Simply include the class somewhere on your page and then use the following to initilize it:</p>

<pre class="lang-js prettyprint-override"><code>    let scopeEditor = new QuillWrapper("ScopeEditor", "Scope", { theme: "snow" });
</code>```

Your html would look roughly like this:

     <input type="hidden" asp-for="Scope" class="form-control" />

```

Body:
I have what I think is a very common scenario. I would normally have this form:

<form method="post">

<textarea name="text"></textarea>
<input type="submit" value="Save" />

</form>

Then with PHP I would capture the data from the form ($_POST['text']) and I could use that in another variable.

Now, I'd like to use Quill so users have a nice rich text editor instead. Quill seems very well suited for this and the documentation is very detailed. However, for some reason I can not find how I can "post" the data to the form. There is one single sample page that sort of does what I want, but I am unable to fully implement this in my sample, and in the quick start guide this rather fundamental (to me) topic is not discussed, and I can not find this in the documentation either.

Is Quill supposed to be used like this? Am I overseeing something? Is there a recommended way to make this work?

This is what I currently have:

<!doctype html>

<title>Test</title>
<meta charset="UTF-8" />
<link href="https://cdn.quilljs.com/1.0.0/quill.snow.css" rel="stylesheet">

<form method="post">

  <button class="ql-bold">Bold</button>
  <button class="ql-italic">Italic</button>

<form method="post">

Hello World!

<input type="submit" value="Save" />

</form>

Body:
A solution I came up with was to make a wrapper class.

class QuillWrapper {

    /**
     * @param targetDivId { string } The id of the div the editor will be in.
     * @param targetInputId { string } The id of the hidden input
     * @param quillOptions { any } The options for quill
     */
    constructor(targetDivId, targetInputId, quillOptions) {

        //Validate target div existence
        this.targetDiv = document.getElementById(targetDivId);
        if (!this.targetDiv) throw "Target div id was invalid";

        //Validate target input existence
        this.targetInput = document.getElementById(targetInputId);
        if (!this.targetInput) throw "Target input id was invalid";

        //Init Quill
        this.quill = new Quill("#" + targetDivId, quillOptions);

        //Bind the two containers together by listening to the on-change event
        this.quill.on('text-change',
            () => {
                this.targetInput.value = this.targetDiv.children[0].innerHTML;
            });
    }
}

Simply include the class somewhere on your page and then use the following to initilize it:

    let scopeEditor = new QuillWrapper("ScopeEditor", "Scope", { theme: "snow" });

Your html would look roughly like this:

<div class="form-group">
     <label asp-for="Scope" class="control-label col-md-2"></label>
     <div id="ScopeEditor"></div>
     <input type="hidden" asp-for="Scope" class="form-control" />
</div>


问:
POST JSON Object via RestTemplate in Spring Boot
I am trying to POST a JSON object to an API endpoint which accepts the data in below format

```<code>{
    "names": [
        "name1",
        "name2",
        "name3"
    ]
}
</code>```

And my post method is as below

```<code>public String post(List<String> names) {

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    JSONObject jsonObject = new JSONObject();
    jsonObject .put("names", names);

    HttpEntity<JSONObject> entity = new HttpEntity<>(jsonObject , headers);

    return restTemplate.postForObject(Constants.URL, entity, String.class);
}
</code>```

When I call the post method I get this error

```<code>org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request
</code>```

I printed out the jsonObject and tried to post it via Postman, it worked.

What is the point that I am missing here?

Any help would be appreciated.

答:
The reason posting the JsonObject directly via a RestTemplate doesn't work in your case is that the RestTemplate is using Jackson Serializer - not the toString method. The Serializer will pick up the internal structure of the class and turn this into a json representation, where as the toString() method gives you the json data that you're expecting.

In your case the internal representation when serialized will look something like this:

"names":{"chars":"namesstring","string":"namesstring","valueType":"STRING"}

This is not the structure you were expecting but it is partly how JsonObject stores your json structure internally. (capturing type information etc).

However, when you call toString(), the JsonObject gives you what you were expecting (i.e. a json representation without all the metadata).

So in short what you think you're sending and what you're actually sending are different. The 400 error you experience is probably because the endpoint you're calling is rejecting the actual format of the data.

You can see this for yourself by debugging the actual call that the RestTemplate makes by using an interceptor. Alternatively, have your client call an echo service to see the payload.

Body:
I am trying to POST a JSON object to an API endpoint which accepts the data in below format

{
    "names": [
        "name1",
        "name2",
        "name3"
    ]
}

And my post method is as below

public String post(List<String> names) {

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    JSONObject jsonObject = new JSONObject();
    jsonObject .put("names", names);

    HttpEntity<JSONObject> entity = new HttpEntity<>(jsonObject , headers);

    return restTemplate.postForObject(Constants.URL, entity, String.class);
}

When I call the post method I get this error

org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request

I printed out the jsonObject and tried to post it via Postman, it worked.

What is the point that I am missing here?

Any help would be appreciated.


问:
pip: why doesn't twine command show up sometimes
I installed twine using the user flag (user scheme, according to the documentation):

```<code>pip install --user twine
</code>```

But I can't use it as a command. For e.g. if I do:

```<code>twine --version
</code>```

It gives me (this is obvious):

```<code>-bash: twine: command not found
</code>```

I read this similar post, but could not find any satisfactory answer (answer is not accepted).

I know I can use:

```<code>python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
</code>```

According to one of the answers.

But, my question is why it doesn't show up?

I checked the twine setup.py and found that it using the entry_points (which helps in setting the twine command). In my assumption, the setup.py is being ignored as it pip using wheel and not sdist during the installation (or to download the package).

答:
For the command

```<code>twine --version
</code>```

shell tries to find twine binary or script in $PATH. If it cannot find the script it emits the error -bash: twine: command not found. This means that pip has installed twine into a directory not in the $PATH. Find out where pip has installed it using the command

```<code>pip show --files twine
</code>```

and add the directory to the $PATH.

For the command

```<code>python3 -m twine --version
</code>```

shell finds python3 and python3 -m looks for a module twine, not a script. python3 -m looks for the module in sys.path, a completely different search path.

Body:
I installed twine using the user flag (user scheme, according to the documentation):

pip install --user twine

But I can't use it as a command. For e.g. if I do:

twine --version

It gives me (this is obvious):

-bash: twine: command not found

I read this similar post, but could not find any satisfactory answer (answer is not accepted).

I know I can use:

python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*

According to one of the answers.

But, my question is why it doesn't show up?

I checked the twine setup.py and found that it using the entry_points (which helps in setting the twine command). In my assumption, the setup.py is being ignored as it pip using wheel and not sdist during the installation (or to download the package).

Body:
For the command

twine --version

shell tries to find twine binary or script in $PATH. If it cannot find the script it emits the error -bash: twine: command not found. This means that pip has installed twine into a directory not in the $PATH. Find out where pip has installed it using the command

pip show --files twine

and add the directory to the $PATH.

For the command

python3 -m twine --version

shell finds python3 and python3 -m looks for a module twine, not a script. python3 -m looks for the module in sys.path, a completely different search path.


问:
Get HWND of a window i clicked on
I'm making a program, with c++ and winapi, where you can click a button, then select a window and it changes the transparency of that window.

I have no problems of making the window transparent, but i can't get the hwnd of a window.

When I use this function the program just crashes (No errors are being printed to the console).

```<code>HWND getHWND(HWND self)
{
  HWND found = GetForegroundWindow();
  if(found == self || found == NULL)
    return getHWND(self);
  return found;
}
</code>```

I tried using this, but I don't like it, because the user has only 2 seconds to click on another window. (I'know I could use a bigger delay, but then it might be to much.. so this isn't optimal)

```<code>HWND getHWND()
{
  Sleep(2000);
  return GetForegroundWindow();
}
</code>```

答:
Don't poll for the active window, especially not with a recursive function.  Just let the OS notify you when a window is clicked.

When the user clicks on your button, you can use SetCapture(), or a mouse hook via SetWindowsHookEx(), to direct subsequent mouse clicks to your app even if they are clicking on someone else's window.  When you detect a click, release the capture/hook, and then use WindowFromPoint() to get the HWND at the location of the click.

Title: Get HWND of a window i clicked on

Body:
I'm making a program, with c++ and winapi, where you can click a button, then select a window and it changes the transparency of that window.

I have no problems of making the window transparent, but i can't get the hwnd of a window.

When I use this function the program just crashes (No errors are being printed to the console).

HWND getHWND(HWND self)
{
  HWND found = GetForegroundWindow();
  if(found == self || found == NULL)
    return getHWND(self);
  return found;
}

I tried using this, but I don't like it, because the user has only 2 seconds to click on another window. (I'know I could use a bigger delay, but then it might be to much.. so this isn't optimal)

HWND getHWND()
{
  Sleep(2000);
  return GetForegroundWindow();
}


问:
How to programmatically recenter PrimeNG dialog when content width expands
New to PrimeNG/Angular here. We have a  element that first opens as a fairly small window. Users can click on a link to expand the dialog. When expanded, the width expands to the right but the dialog stays positioned as when it first opened.

I understand there's supposed to be a dialog center() method but I can't figure out how to wire it up. Thanks!

答:
Got it to work by using this as an inspiration: PrimeNG p-dialog positionTop

Basically, I'm setting an 'expanded' class on the ui-dialog element itself, as well as some inner elements, and then programmatically setting the left position.

```<code>private centerDialog() {
    const dialogElement = <HTMLElement>document.querySelector('.ui-dialog');

    if (!dialogElement) {
      setTimeout(() => {
        this.centerDialog();
      }, 100);
    } else {
      this.toggleExpandedClass();
      dialogElement.style.left =
        window.innerWidth / 2 - dialogElement.clientWidth / 2 + 'px';
    }
  }
</code>```

Body:
Got it to work by using this as an inspiration: PrimeNG p-dialog positionTop

Basically, I'm setting an 'expanded' class on the ui-dialog element itself, as well as some inner elements, and then programmatically setting the left position.

private centerDialog() {
    const dialogElement = <HTMLElement>document.querySelector('.ui-dialog');

    if (!dialogElement) {
      setTimeout(() => {
        this.centerDialog();
      }, 100);
    } else {
      this.toggleExpandedClass();
      dialogElement.style.left =
        window.innerWidth / 2 - dialogElement.clientWidth / 2 + 'px';
    }
  }