If you don't know Python, perfect! You can skip this section.
If you have a background in programming Python, at first glance sneq will seem familiar. You may be tempted to import various Pythonisms into sneq, but you must be aware that sneq is not Python.
Trying to code sneq like you would code Python will make you upset because it will crash.
In sneq, the only time you are allowed to use anonymous function arguments is for built-ins. Functions that are not built-in require you to name your arguments.
def foo_bad(bar): # Not okay.
return bar
def foo_good(bar=None): # Good!
return bar
unique([1, 3, 2, 2]) # Good! "unique" is a built-in.
Functions must be pure. In order to access a variable within the scope of another function, it must be passed to that function.
foo = "bar"
def do_a_thing_bad():
return foo # Not okay. "foo" does not exist in the functions scope.
def do_a_thing_good(foo=foo):
return foo # Good! "foo" is passed into the function as "foo".
Both lists and dicts are immutable.
foo = [1, 2, 3]
foo[1] = 5 # Not okay. You are not allowed to mutate "foo".
foo = [1, 5, 3] # Good! "foo" has been redeclared.
foo = { "bar": 12345 }
foo["bar"] = 42 # Not okay. You are not allowed to mutate "foo".
foo = { "bar": 42 } # Good! "foo" has been redeclared.
Getting sublists is a little more complicated. You need to use the utility function sublist.
lst = [1, 2, 3, 4, 5, 6]
lst = lst[1:-2] # Not okay. This is Python syntax.
lst = util.list.sublist(start=1, end=-2) # Good!
Type mismatches will cause a crash. To prevent type mismatches, detect types and using branching logic to handle expected cases.
two = 2
two_str = "2"
foo = two + two_str # Not okay. This will crash.
foo = None
if isinstance(foo_str, 'str'):
foo = two + dec(foo_str) # Good! We cast "foo_str" to the right type.
Unlike Python, numbers are not integers by default. Like in JavaScript, numbers can have decimal places by default. Unlike JavaScript, numbers are are decimals rather than floats. Because of this, you will not encounter floating point errors, but if you want to use integers, you must cast them with int
.
foo = 0.1 + 0.2 # 0.3, not 0.30000000000000004.
foo = 10 # This is a decimal, not an integer.
foo = int(10) # This is an integer and will crash if you add it to a decimal.
Unlike Python, you can not have a statement without an assignment or without returning something.
2 + 2 # Not okay. You need to assign this to something.
foo = 2 + 2 # Good! The value has been assigned.
def foo():
return # Not okay. The function does not return anything.
def foo():
return None # Good! The function returns None.
In Python normally the first startment in boolean logic executes before all the others, but this is not the case in sneq. All statements in conditional logic will be evaluated in sneq before the comparison occurs.
# Not okay! 2 + "2" will be evaluated still and cause a crash.
foo = True or bool(2 + "2")
In your sneq journey you will find many inconsistencies with Python, because sneq is not Python. There are no classes, there is no try: catch: finally:, and other parts of the syntax are dramatically simplified. Rest assured that if you need to do something that could be done in simple Python, it can probably also be done in sneq, potentially with some extra leg work.
Reservation scripts can be created and tested in the Administration -> Resources -> Scripts panel.
All reservation scripts are found under the "Reservation" tabs in the scripts administration page. To create a script, click the "Create Reservation Script" button in the top right.
You will be presented with the IDE and a script of return True
. To learn more about the IDE and its workings, read up on the IDE section of the documentation.
Reservation scripts are special in what global environmental context is set. In particular, you will have access to three dictionaries by default, reservation, script, and user_logged_in. These are accessible in your script by simply using their variable names.
The reservation
dictionary contains all the information about the reservation that is being made. We can use this and the Exception
to gate access to a resource that has this script attached.
Below is a simple script that checks the purpose
field of a reservation and then displays an error to the user if the purpose contains the word "foo".
if "foo" in reservation["purpose"]:
return Exception("No foos allowed in purpose.")
return True
Try copy and pasting this into the IDE, then click the "Save" button on the bottom right to save the script. Finally, test the script by clicking the "Test Reservation or Request" button in the top right, which will bring up the testing window. There, select a resource and then try entering "foo" into the purpose input box. You should be greeted by the error "No foos allowed in purpose."
The return True
at the end of the script signals that the reservation can otherwise continue and be made by the end user.
Another caveat is that multi-resource reservations are different from single-resource reservations in their structure. Multi-resource reservations have a parent reservation and then a list of single-resource child reservations in the reservation_children
key. For ease of use, when you have a script that takes into account resources you will want to use the function util.reservations.all()
. This returns reservations as a list, so a single-resource reservation will be [reservation]
while a multi-resource reservation will be [reservation_1, reservation_2, ... ]
. Please refer to the structure of the reservation
object for more information.
Exceptions and return values can also be used to modify the user interface. For example, if you wanted to add a disclaimer to the reservation when it is not blocking, you can have it return the following default dictionary: { 'disclaimer_top': "## Foo is forbidden as a reservation purpose." }
. Putting it all together, the script now looks like:
if "foo" in reservation["purpose"]:
return Exception("No foos allowed in purpose.")
return { 'disclaimer_top': "## Foo is forbidden as a reservation purpose." }
When you test the script and do not have "foo" in the purpose text input, you will now see a helpful disclaimer about the purpose.
Now that we have a functional script, to use it we simply add it to a resource. This can be done in either the scripts tab when editing a resource, or on the resources tables administration page with the scripts column being selected. I titled my script "No foos in purpose".
Now that the script is added to the Testy resource, I can navigate to the Testy resource calendar and use the script in my production site environment. While this script is contrived, QReserve gives you access to a rich amount of data from both the resource and the API which you can take advantage of to ensure that resource availability is custom tailored to your individual needs. Have a look at some of the other example issues and solutions that scripts can resolve.
Rate scripts determine the rate for a single resource in a reservation. You can use them to dynamically adjust the rate for different situations. To begin making rate scripts, first add a rate to a resource by navigating to the resource edit page, then selecting "Add Rate" in the Reservations -> Rates panel.
Now that your resource has a rate, we can create a rate script and begin testing it with this resource and rate. To create a rate script, go to the Resources -> Scripts page of the administrative section, select the "Rates" tab, and then select "Create Rate Script" in the top right corner.
After creating the rate script, you will be presented with the script editing IDE. This is similar to the script IDE for reservations, except in order to test your script you will need to first select the resource and rate you wish to test.
Another key difference with rate scripts is that there is no concept of exceptions that block the reservation -- rate scripts must return a dictionary containing at minimum a rate
, rate_unit
, and rate_basis
. Refer to this documentation for more information about the structure expected for the return value of rate scripts. Rate scripts not returning this information or throwing exceptions will crash.
Another difference in rate scripts is that the original rate that the reservation was attached to is accessible to the user in script['rate']
. To be helpful, the information in script['rate']
is provided to you when testing after you have selected a resource and a rate.
As in reservation scripts, all the details about the reservation itself are available within the reservation
dictionary.
When updating the rate and rate description, the rate description in the UI itself will also dynamically update.
Once you are happy with your script, you can add it to your rate by returning to the resource editing page and clicking the "Add Script" button on the rate.
Now your rate script will execute whenever a reservation is made and dynamically set the rate for your resource.
Workflow scripts can be used to alter tasks in subscriptions or triggered workflows. The foremost use of this is for generating emails and altering credentials.
Let's create a new trigger based on reservations being modified to show how we can use scripts to customize an email. We'll start by going to Workflows -> Triggers in the administrative panel, then selecting "Create New Trigger".
Here a testing trigger has been set up so that it triggers on reservation modifications for any reservation for the resource Testy.
Now we can add some tasks below. The first task I will add is a script and the second task will be an email.
In order for the email to use the output of the script, we need to assign a task ID to the script task and then put that task ID into the "Input Script Task ID" text box. This will indicate to the email task that it should use the output of the script task when generating an email. The script task here is assigned the id script_task_id
.
There is some extremely helpful information about the expected input of the email task in script terms hidden in the help text for "Input Script Task ID":
We can use the information here to craft an email by writing a script. Below is an example script that we can use to test the feature.
return {
'subject': "This is a subject line",
'body': "Hello " + reservation["reserved_for"]["display_name"] +
', thank you for modifying your reservation',
}
Now we're ready to test our trigger. To do this effectively we need to make a reservation, then modify the reservation, then get the reservation ID for the modified reservation. Go to the calendar page for the resource and do so, and then click on the reservation again after modifying it and click on the reservation ID in the bottom left of the reservation viewing modal to copy it to the clipboard.
Now, navigate back to the the trigger you are working on (it should be in the Paused tab). From here, go to the "Testing" box and paste the reservation ID.
Now try the "Test Event Hook Run" button. You should be greeted with a successful test run along with the output of each task. You can click on each individual output to expand them. As you can see, the body of the email includes the display name of the user the reservation was for.
Finally, to activate your new trigger workflow, navigate to the top of the page and select "Activate or Resume Trigger".
This will enable your trigger, making it so that this email is sent to every user who modifies their reservation for the resource Testy.