Performing sensitive Django actions while masking/hiding underlying URL - python

I have a simple Django view that confirms the deletion of an instance. On this page is a simple form containing two buttons, "Cancel" and "Delete."
The Cancel button simply returns the user to the page from which the original delete button was pressed.
The Delete button jumps to a second view that performs the actual action. Thus, my URLs are defined as follows:
url(r'^confirmDeleteItem/(?P<item_key>\w+)$', 'confirm_delete_item'), # Confirms
url(r'^deleteItem/(?P<item_key>\w+)$', 'delete_item'), # Performs the action
On the confirmation page, the form is defined with a POST action that visits the second URL:
<form action="/squash/deleteItem/{{ item.key }}/" method="POST">
...
</form>
The problem I have with this is that the Items are fairly large (they store lots of data) and sensitive, so I'd like to force the user to jump through the confirmation hoop every time.
I would like to either prevent the User from visiting the /deleteItem/ page manually, or just hide the browser's loading of this page to avoid from it becoming stored in the history, accidentally bookmarked, etc.
Is wrapping the action in an AJAX call the best way to solve this problem, or are there more standard/preferred solutions? Thanks!

How about setting a session variable in confirmDeleteItem view (i.e. prepareToDelete = item.key) and checking in deleteItem view whether this session variable exists and whether the value match the item.key? Then you'd just need to remove it after actual delete occurred.

A quick idea which I'm not sure of, but should work in your case.
Check your referer in your deleteItem view. Like in this snippet of code. If user didn't come to the delete view from confirmDelete view, redirect him to the appropriate confirmDelete view.

Related

Django: Change default parameter appended in the URL in forms

I have a form like this:
<form method="get">
<button name="load" type="submit" value="{{game.game_id}}|{{game.champion}}">Show user's stats</button>
</form>
When the user submits the form, the parameter will automatically be appended into the URL like this
www.example.com/?load=5293733926|99
However, I want to add manually another parameter to the URL making the URL look like this:
www.example.com/?load=5293733926|99&page=2
If I manually type this to the URL, it goes to where I want. How should I do this? I tried adding:
action="./?load={{game.game_id}}|{{game.champion}}&page={{game.page}}"
and multiple other variants.
But it doesn't work, it redirects to www.example.com/?load=5293733926|99.
Context: I have a huge list of dictionaries and if I show them all at the same time and process them, it takes a lot of time. I divided the list with Django's paginator. The forms act as a load more. By default, I show some data from the dictionary and when the user clicks the form button, some extra information is showed. However, if someone clicks the form button in page 2 www.example.com/?page=2, Django would redirect the user to the first page after processing the data. In order to redirect the user to the submitted button's page, I would need to redirect the user to www.example.com/?load=5293733926|99&page=2
It's my first time asking a question here, if some more data needs to be provided, please let me know. Thanks!
P.D: This is a piece of the list of dictionaries:
{"matches":[{"platformId":"EUW1","gameId":5304134881,"champion":99,"queue":420,"season":13,"timestamp":1622752495039,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":5303158955,"champion":99,"queue":420,"season":13,"timestamp":1622727838679,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":5302981978,"champion":99,"queue":420,"season":13,"timestamp":1622718693646,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":5302393136,"champion":64,"queue":400,"season":13,"timestamp":1622666939087,"role":"NONE","lane":"JUNGLE"},{"platformId":"EUW1","gameId":5301715149,"champion":103,"queue":420,"season":13,"timestamp":1622640835828,"role":"DUO","lane":"MID"},{"platformId":"EUW1","gameId":5301720723,"champion":99,"queue":420,"season":13,"timestamp":1622638646725,"role":"SOLO","lane":"MID"},{"platformId":"EUW1","gameId":5301040032,"champion":497,"queue":420,"season":13,"timestamp":1622625968514,"role":"DUO_SUPPORT","lane":"BOTTOM"},
With the form, I would get the champion number and the gameId in order to process the data of the game. A piece of the game's data looks like this:
{"gameId":5304134881,"platformId":"EUW1","gameCreation":1622752495039,"gameDuration":1983,"queueId":420,"mapId":11,"seasonId":13,"gameVersion":"11.11.377.6311","gameMode":"CLASSIC","gameType":"MATCHED_GAME","teams":[{"teamId":100,"win":"Win","firstBlood":false,"firstTower":false,"firstInhibitor":true,"firstBaron":true,"firstDragon":false,"firstRiftHerald":true,"towerKills":11,"inhibitorKills":3,"baronKills":1,"dragonKills":4,"vilemawKills":0,"riftHeraldKills":1,"dominionVictoryScore":0,"bans":[{"championId":63,"pickTurn":1},{"championId":111,"pickTurn":2},{"championId":25,"pickTurn":3},{"championId":55,"pickTurn":4},{"championId":235,"pickTurn":5}]},{"teamId":200,"win":"Fail","firstBlood":true,"firstTower":true,"firstInhibitor":false,"firstBaron":false,"firstDragon":true,"firstRiftHerald":false,"towerKills":2,"inhibitorKills":0,"baronKills":0,"dragonKills":1,"vilemawKills":0,"riftHeraldKills":0,"dominionVictoryScore":0,"bans":[{"championId":235,"pickTurn":6},{"championId":62,"pickTurn":7},{"championId":234,"pickTurn":8},{"championId":25,"pickTurn":9},{"championId":777,"pickTurn":10}]}],"participants":[{"participantId":1,"teamId":100,"championId":98,"spell1Id":4,"spell2Id":12,"stats":{"participantId":1,"win":true,"item0":1054,"item1":3075,"item2":3047,"item3":3068,"item4":3748,"item5":3001,"item6":3340,"kills":10,"deaths":5,"assists":20,"largestKillingSpree":5,"largestMultiKill":2,"killingSprees":2,"longestTimeSpentLiving":936,"doubleKills":1,"tripleKills":0,"quadraKills":0,"pentaKills":0,"unrealKills":0,"totalDamageDealt":163173,"magicDamageDealt":58700,"physicalDamageDealt":81055,"trueDamageDealt":23417,"largestCriticalStrike":0,"totalDamageDealtToChampions":28604,"magicDamageDealtToChampions":15265,"physicalDamageDealtToChampions":13021,"trueDamageDealtToChampions":317,"totalHeal":7157,"totalUnitsHealed":1,"damageSelfMitigated":49148,"damageDealtToObjectives":7345,"damageDealtToTurrets":7345,"visionScore":16,"timeCCingOthers":36,"totalDamageTaken":33907,"magicalDamageTaken":9592,"physicalDamageTaken":18466,"trueDamageTaken":5848,"goldEarned":14812,"goldSpent":13775,"turretKills":3,"inhibitorKills":1,"totalMinionsKilled":163,"neutralMinionsKilled":8,"neutralMinionsKilledTeamJungle":4,"neutralMinionsKilledEnemyJungle":4,"totalTimeCrowdControlDealt":264,"champLevel":18,"visionWardsBoughtInGame":1,"sightWardsBoughtInGame":0,"wardsPlaced":10,"wardsKilled":0,"firstBloodKill":false,"firstBloodAssist":false,"firstTowerKill":false,"firstTowerAssist":false,"firstInhibitorKill":false,"firstInhibitorAssist":false,"combatPlayerScore":0,"objectivePlayerScore":0,"totalPlayerScore":0,"totalScoreRank":0,"playerScore0":0,"playerScore1":0,"playerScore2":0,"playerScore3":0,"playerScore4":0,"playerScore5":0,"playerScore6":0,"playerScore7":0,"playerScore8":0,"playerScore9":0,"perk0":8437,"perk0Var1":2259,"perk0Var2":1714,"perk0Var3":0,"perk1":8446,"perk1Var1":3620,"perk1Var2":0,"perk1Var3":0,"perk2":8444,"perk2Var1":2027,"perk2Var2":0,"perk2Var3":0,"perk3":8451,"perk3Var1":269,"perk3Var2":0,"perk3Var3":0,"perk4":9111,"perk4Var1":2635,"perk4Var2":600,"perk4Var3":0,"perk5":9104,"perk5Var1":12,"perk5Var2":40,"perk5Var3":0,"perkPrimaryStyle":8400,"perkSubStyle":8000,"statPerk0":5005,"statPerk1":5008,"statPerk2":5002},"timeline":{"participantId":1,"creepsPerMinDeltas":{"10-20":5.199999999999999,"0-10":4.3,"20-30":5.7},"xpPerMinDeltas":{"10-20":562.5,"0-10":446.4,"20-30":638.2},"goldPerMinDeltas":{"10-20":418,"0-10":304.1,"20-30":514.8},"csDiffPerMinDeltas":{"10-20":-0.8000000000000003,"0-10":-2.2,"20-30":1.1999999999999997},"xpDiffPerMinDeltas":{"10-20":56.50000000000003,"0-10":40.79999999999998,"20-30":232.3},"damageTakenPerMinDeltas":{"10-20":666.8,"0-10":686,"20-30":1504.1},"damageTakenDiffPerMinDeltas":{"10-20":-519.2,"0-10":18.00000000000003,"20-30":-1596.5},"role":"SOLO","lane":"TOP"}},

How do I set the same session variable for all views programmatically?

I want to create a "Sign up for our newsletter" pop up that has a "No thanks" button.
I want Django to remember that the user clicked the "No thanks" button.
What I know is that sessions/cookies are set in their respective views. I see examples on StackOverflow of them set in the home/index view. What if the user visits a different page? Then the session variable won't be set unless they visit that one page.
I'd like for the same variable set regardless of what page they view.
Once a session variable is set, it is set across your whole app. If you have access to the request, you get it like this:
request.session['idempresa']
You set it once in a view (or in some middle ware) and it's available anywhere you have access to a request. That's how http sessions work.

Passing form parameters between views in Pyramid

I am creating a form that requires user confirmation before submitting the data. I would like a seperate confirmation page because I need to display quite a bit information about how the form data will be processed. I was wondering if there was a pythonic way to pass data between forms in Pyramid.
Submitting the form takes the user to the confirmation page. Thus, the view for the confirmation has the form data stored in request.POST. I was wondering if there was a clean way to pass along all of this data to the final view once the user hits 'submit' on the confirmation page. I would also like to add a boolean variable, confirmed, to the dictionary of parameters.
This is not a Pyramid-specific answer, but two common approaches to this problem are:
Store the data in a session.
Store the data as a hidden form on the confirmation page, and resubmit with "confirmed"
I like 2 much better because it's a stateless method. You can also use the exact same form processing logic, and just check for the presence of your "confirmed" POST variable to decide which action to take and view to show (i.e, either the "please confirm" view, or processing and the "processed" view.)

URLs and side effects (Django)

I'm wondering if it's considered okay (particularly, in Django) to have a URL that's only intended for actions with side effects, that's only intended to be accessed by POST, and that is basically invisible to the user. Let's say, for the sake of making this concrete, I have a little messaging system on my site, and from their inbox, a user should be able to do a bunch of things like:
Delete a message
Mark a message as read
Report a message as spam
With all of those things causing a page refresh, but leading back to the same page. I'm wondering how to design my URLs and views around this. I see (at least) two options, and I have no idea which is more idiomatic.
Option 1)
Have a separate URL and view for each action. So, /inbox/delete-message/ maps to views.delete_message, and so on. At the end of each of those views, it redirects back to /inbox/.
I like the way things are clearly separated with this option. If a user somehow finds themselves sending a GET request to /inbox/delete-message/, that presents a sort of weird situation though (do I throw up an error page? silently redirect them?).
Option 2)
Use the same URL and view for each action, and have a POST parameter that identifies the action. So I would have one rather long inbox view, which would have a bunch of if statements testing whether request.POST['action'] == 'delete', or request.POST['delete'] == 'true' or whatever.
This option feels less clean to me, but I also feel like it's more common.
Which would be preferred by Djangonauts? Or is there another option that's better than either of the above?
A modified option #1 is the best approach. Consider this: suppose we weren't talking about a web app, but instead were just designing an inbox class. Which do you like better, a number of methods (delete_message(), mark_as_spam(), etc), or one big method (do_stuff(action))? Of course you would use the separate methods.
A separate URL for each action, each with a separate view, is far preferable. If you don't like the redirect at the end, then don't use it. Instead, have a render_inbox(request) method that returns an HttpResponse, and call the method at the end of each of your views. Of course, redirecting after a POST is a good way to prevent double-actions, and always leaves the user with a consistent URL.
Even better might be to use Ajax to hide the actions, but that is more involved.
I don't think there's anything wrong with either option, but #2 is potentially better from a performance standpoint. After the action is posted you can render the inbox without a redirect, so it cuts down on the HTTP traffic.
If you're writing a web 2.0 messaging app, you would be using AJAX calls and wouldn't be loading a new page at all. The process would proceed like so:
User clicks [delete] for a message. This button has a javascript action bound to it. This action does the following:
i. Change the UI to indicate that something is happening (grey the message or put up an hourglass).
ii. Send a request to /messages/inbox/1234/delete. (where 1234 is some identifier that indicates which message)
iii. When the response from the server comes back, it should indicate success or failure. Reflect this status in the current UI. For example, on success, refresh the inbox view (or just remove the deleted item).
On the server side, now you can create a URL handler for each desired action (i.e. /delete, /flag, etc.).
If want to use an even more RESTful approach, you would use the HTTP action itself to indicate the action to perform. So instead of including delete in your URL, it would be in the action. So instead of GET or POST, use DELETE /messages/inbox/1234. To set a flag for having been read, use SET /messages/inbox/1234?read=true.
I don't know how straightforward it is in Django to implement this latter recommendation, but in general, it's a good idea utilize the protocol (in this case HTTP), rather than work around it by encoding your actions into a URL or parameter.
I agree that #2 is a better approach.
But take care with overloading the submit <input /> with different methods -- if a user is using it with keyboard input and hits enter, it won't necessarily submit the <input /> you're expecting. Either disable auto-submit-on-enter, or code things up so that if there is more than one thing that submit can do, there's another field that sets what the action should be (eg a 'delete' checkbox, which is tested during a request.POST)
If you went with #1 I'd say that a GET to a POST-only view should be met with a 405 (method not supported) - or, failing that, a 404.

Rendering common session information in every view

I'd like to output some information that depends on session data in Django. Let's take a "Login" / "Logged in as | Logout" fragment for example. It depends on my request.session['user'].
Of course I can put a user object in the context every time I render a page and then switch on {% if user %}, but that seems to break DRY idea - I would have to add user to every context in every view.
How can I extract a fragment like that and make it more common?
Use template inheritance to derive all of your templates from a common base that suitably uses the common parts of the context, and make all your contexts with a factory function that ensures the insertion in them of those common parts.
Are you trying to make certain areas of your site only accessible when logged on? Or certain areas of a particular page?
If you want to block off access to a whole URL you can use the #login_required decorator in your functions in your view to block certain access. Also, you can use includes to keep the common parts of your site that require user login in a separate html that gets included, that way you're only writing your if statements once.
You may want to use a context processor that includes logic and place it into a variable you can use in any of your pages without adding it to each call.
See more info at How to pass common dictionary data to every page in django

Categories

Resources