Learn the Python Anvil Framework

FOR ABSOLUTE BEGINNERS

By: Nick Antonaccio (nick@com-pute.com)

This tutorial demonstrates how to use the Python 'Anvil' framework
to make modern web apps that run on every common platform
(Windows, Mac, Linux, Android, iOS, Chromebook, etc.), in the
easiest, most productive way possible. You'll learn how to create
several dozen useful apps with Anvil, using nothing but Python
code, in just a few hours. You don't need to have any previous
experience to complete this tutorial.

Contents:

1. How to View This Tutorial Best on Your Device
2. What is Anvil?
2.1 Why Choose to Use Anvil (a pure Python tool) to Build HTML/CSS/JS Web Apps
3. Getting Started
3.1 The Anvil IDE and a Hello World app
3.2 Variables
3.3 Adding a Button and Some Action
3.4 Adding a Text Entry Field, Concatenation, and Saving Code Versions
3.5 Making Widgets Work Together Interactively, Built-In Responsive Design, and Errors
3.6 Publish Your App with One Click
3.7 Finishing up With Hello World, Saving to GIT and Backing Up
4. Off and Running With a Live Webcam Viewing App - DropDowns and URLMedia
5. Coin Flip App - Using Python Libraries, Random and Time
5.1 Using Functions in Python Libraries
6. A 'Time Between' Dates App, Conditional Evaluations and Dates
7. Math Test - Eval(), Str(), Custom Function Definitions, and More Error Handling
8. A Multi-Page Information Web Site App - Navigation Links
9. A Simpler Way to Navigate Multi-Page Apps
10. Chat App - Anvil's Built-in Database System
10.1 Using the Database
11. Cash Register
11.1 Generating Sales Reports
12. Birthday List App - CRUD with Server Functions and Multiuser Account Management
12.1 Our First Server Code
12.2 Adding User Accounts
13. Image Manipulation App - Media in the Database and More Refactoring
14. 999 Bottles of Beer - 'For' Loops, Collections and Some More Python
14.1 Slices, Strings, Dictionaries, Enumeration, Nested and Zipped Loops, List Comprehensions
15. Pig Latin App, Using 2 Different Loop and String Approaches
16. Mad Libs - Using Google to Find More Python Code
16.1 Version 1
16.2 Version 2
17. Email App
18. Calculator App - Creating UI Design Layouts with Pure Anvil Code
19. Weather App - Using HTTP REST APIs in Anvil
20. Database and IP Address Service App Examples - Creating HTTP REST APIs with Anvil
21. Hangman Game App - Drawing Canvas Graphics in Anvil
22. Guitar Chords App - More Graphics Drawing, for a Higher Purpose!
23. Classified Ad Site - More About Navigation Layout and Relational Databases
23.1 Navigation
23.2 Using Linked Data Tables
23.3 Adding Related Rows to the DB, from UI Widgets
24. Uplink App - Accessing an Sqlite Database on an Android Phone with Anvil
24.1 What is Uplink?
24.2 Just 'pip install anvil-uplink'
24.3 Porting Anvil's PostgreSQL Uplink Example to Sqlite Running on my Cell Phone
25. Learning To Think In Code
25.1 A General Approach, Using Outlines and Pseudo Code
26. Case Studies
26.1 Markdown Editor
26.2 FTP File Manager
26.3 Shift Clock
26.4 Poll Builder
26.5 Count Words in a Text Area
26.6 Typing Tutor
26.7 More Case Studies
27. Why is all this so Valuable - Even If You're Not A Professional Developer?
28. This is Just the Beginning! Where to go from here...
28.1 If You Want More Basic Info About Computing
29. About the Author

1. How to View This Tutorial Best on Your Device

In some desktop browsers, the text and images on this page may initially appear small. Press 'CTRL' and '+' on your keyboard to enlarge the document so it fills your screen. If you're reading on a mobile device, pinch and zoom to expand any images/code. If you have a very small screen, turn your phone sideways lengthwise to rotate the view into landscape mode. That will provide the largest possible view of the document contents. You may want to refresh your browser after rotating into landscape mode, so that every bit of text fills the screen as neatly as possible. You can also choose to view in 'simplified' mode, if your mobile browser suggests that option.

2. What is Anvil?

Anvil is a 'full-stack' framework used to build web applications with Python. The Anvil IDE ('Integrated Development Environment') runs in a web browser, so it can be used instantly on any common OS (Windows, Mac, Linux, Android, iOS, Chromebook, etc.), without any installation. It includes a rich visual drag-and-drop user interface builder, for designing front-end app layouts that run in a browser, without having to use any HTML, CSS, or JavaScript. Material Design and other modern styles are included by default, and full access to edit all underlying style code is provided for advanced users. Any layout that can be accomplished with the visual builder, can also be produced with pure Python code (the drag-and-drop builder is not required, if you prefer to create layouts with code).

Anvil includes a powerful built-in database system based on PostgreSQL, with a visual table manager and 'ORM', so it can be used entirely in Python, without needing any SQL query code. You can also connect to any external database system supported by Python, whenever needed. Anvil provides a full secure Python server environment, with complete access to the entire ecosystem of powerful Python libraries and features. Functions on the server back-end interact seamlessly with front-end Python UI code running in the user's browser, all in a single integrated coding environment. The entire full-stack development process in Anvil is completed only with simple Python objects, methods, and data structures, that work together cohesively, requiring absolutely no other language code or complex tool chain components.

Anvil also includes an 'uplink' feature, which allows it to connect with any Python code running on any remote computer: desktop machines, mobile devices, robots, IoT hardware, cloud hosted virtual environments - any connected device that can run Python. This allows developers to easily share and control functions running on virtually any common platform (including Jupyter notebooks running anywhere outside Anvil). No complex socket connections or HTTP API calls are required - just one line of code enables functions on any remote machine to be connected simply to an Anvil App, to share data and functionality - all using Python language structures, with direct access to well known libraries.

Anvil does also enable easy remote 'web API' creation and publication, so that apps written in other languages can connect with data processed by Anvil apps, using typical REST interactions over HTTP. Anvil includes powerful features such as built-in user authentication (with common email confirmation routines, as well as Google, Facebook, and Microsoft 1-step sign-in support), instant integration with Stripe payment processing (to implement e-commerce credit card payments in minutes), built-in email services (and/or connect to your own third party email servers), built-in Google and Microsoft services integration (direct access to data in Google Docs, Files, Sheets, Images, Maps, MS Azure API calls, etc.), integrated PDF printing, file uploading and downloading, image manipulation and canvas graphics/drawing, simple integration of third party web APIs (enable video conferencing with a few lines of Daily API code, send SMS messages with Twilio's API, generate a geolocation app with Mapbox, etc.), and complete access to all underlying HTML, CSS, and JavaScript code to enable unlimited low level style tweaking (although you never need to use anything but Python to build complete, full stack apps with Anvil).

2.1 Why Choose to Use Anvil (a pure Python tool) to Build HTML/CSS/JS Web Apps

You can build data-science, machine learning and artificial intelligence apps easily with Anvil, because it provides full access to every library in the normal Python ecosystem (Numpy, Pandas, Matplotlib, Tensorflow, PyTorch, Theano, OpenCV, all the standard library, etc...). JavaScript and other popular language ecosystems don't provide access to these same best-of-breed scientific software development tools. You can also build secure and beautiful e-commerce web sites more quickly and easily than you can with traditional web development tools, using nothing but Anvil's built-in Python APIs. Anvil's full stack environment is well suited to creating rich, secure multi-page apps and large web sites with user management features - much more so than other Python tools such as Streamlit, Remi, or PySimpleGUI (those libraries are more suited to creating quick in-house apps with fewer layout options and simpler back-end features). Anvil has been shown to be 7 times more productive than typical web development tools, for building real commercial apps. You can use Anvil to build powerful modern database and multi-user CRUD business systems in minutes, hours and days, instead of days, weeks, and months, as might be expected with JavaScript and other mainstream tools - and the entire process is far more enjoyable. With the uplink feature, you can use Anvil to quickly create front-end GUIs that control/interact with third party web servers, your home computer, Android and iOS mobile devices, IoT components in your home/automobile, Raspberry Pi driven 'maker' devices and robots, etc. No other high level language can be used for this sort of hardware control, in the ways that Python can. There are very few types of typical user applications that can't be created with Anvil, quickly and easily. Anvil can scale up to the most complex needs of virtually any commercial environment, and absolute beginners can start working with it to complete truly useful tasks, within a few hours.

Anvil provides a hosted solution which enables instant application deployment, without any of the time-consuming tasks typically associated with web app publication. Anvil eliminates all the complexity of connecting HTML, CSS, JavaScript, and front-end frameworks such as React and Angular, to back-end server languages such as PHP, Java, and NodeJS, database systems and SQL query language, data transfer formats such as Json, server software such as Apache, site management tools such as Cpanel and FTP file upload systems, Linux terminal commands and OS configuration tasks, as well as troubles related to resolving version differences between so many divergent tools in a complex tool chain, cross platform compilation and packaging requirements, multiple app store submission and application processes, etc., that make the development process of modern apps such a difficult undertaking. With Anvil, you create every piece of an app with a single language, using a single unified toolkit that runs instantly in any browser on any machine, and which deploys to any connected device running any common OS, with the click of a button. Upgrades and version changes to your software are handled automatically, so users of your app are always working with the most up-to-date version of your code (GIT integration and version control are built into Anvil).

If you're not familiar with the sorts of troubles common in modern software development, take a look at https://www.youtube.com/watch?v=VsTaM057rdc . That video demonstrates a typical sort of configuration routine needed on MS Windows, to deploy a trivial hello world app to the Android platform. Deploying that same app to other platforms such as iOS requires even more messy configuration, and requires developers to own a Macintosh desktop PC to compile each incremental code change for iPhone or iPad - and that doesn't even address the process of submitting to each of the separate platform app stores. Setting up requirements on any single development machine, and then compiling/publishing, to any single deployment OS, can take hours of work for each combination of platforms. This requires enormous time and effort, and deep detailed experience with each platform ecosystem - which can actually eclipse the effort required to write code for simple apps' functionality. The normal set of hoops you must jump through to write cross-platform applications is a *tremendous mess*, even when using tools that ease work by implementing the same coding language on each platform. Anvil eliminates all those typical problems, and further simplifies the development of most common sorts of client-server apps, without sacrificing power or deep capability in any way (in fact, Python ecosystem integration alone enables even greater inherent capability than is available in many other cross-platform development systems). Anvil is a robust tool suited to professional development work in commercial environments where security, enterprise-level technical capability and completely flexible design are required. It just also happens to be easy enough for absolute beginners to use immediately.

Because Anvil only requires a web browser to run or to develop apps, you can jump back and forth between any home computer, your phone, or a tablet, to continue working with the same application, without any setup or code changes required on any machine. Anvil's online Integrated Development Environment and deployment service offerings include a free starter account option, as well as a range of commercially hosted plans that scale up to handle demanding enterprise data processing loads. You can also choose to use the totally free Anvil Server software, which is fully functional and completely open source, so it can be installed on any computer(s) or cloud account(s) you own, and you can control your deployment implementations however you prefer: run on an in-house server completely disconnected from the Internet, or in Docker containers on Amazon AWS, Google Cloud, Microsoft Azure, DigitalOcean, and other cloud hosts.

If you've never programmed in Python, you can run through the examples on this web site - they're fully explained and easy enough to understand, even if you've never had any previous Python or other programming experience. This tutorial will teach you enough about programming, from the ground up using Python and the Anvil API, to actually start making applications. You'll complete more than 20 projects by the end of the tutorial. Then you can go through a quick language introduction (try https://www.youtube.com/watch?v=VchuKL44s6E), and begin reading the Anvil tutorials. Anvil's concise documentation steps new users quickly through many dozens more real-life projects, across a wide scope of app types, in a matter of days. The learning curve for Anvil is comparable to no-code app tools (similar in difficultly to learning Excel, Appgyver, or Bubble, for example), for getting high-level work done, but it provides far deeper fine grained control and access to extended capabilities, without the limitations imposed by non-technical tools. Anvil developers have full access to the enormous Python ecosystem which drives so much of today's modern technology. So by using Python, the time spent learning Anvil's native language enables users to connect with all of the rest of the technology world, using a broadly accepted standard toolkit. Python is not just another viable language, but the #1 most demanded skill in many popular sectors of the tech industry. Python is used to power some of the busiest web sites in the world: Instagram, Google, Spotify, Netflix, Uber, Dropbox, Pinterest, Instacart, Reddit, Lyft, and many others. You can use MicroPython and CircuitPython to build embedded hardware devices, using Raspberry Pi products and microcontroller boards by dozens of manufacturers (C and C++ are really the only other languages that can be used for this work, but C/C++ is not well implemented for high level work such as web site development). If you want to work in the tech field, Python coding is one of the most needed and best paying skills to acquire. A large majority of computer science school programs use Python as the primary language in their curriculums, and there is support for Python everywhere in the tech community. If you have any questions about how to use Python for any purpose, you'll typically find detailed code and answers to get work done, with a quick Google search. If you're just interested in being a hobbyist or home user, or only hope to accomplish a few quick technical goals, you can get started working quickly in Anvil, with no prerequisites beyond the ability to use a web browser.

Using Anvil is one of the most productive ways to create actually useful apps of all sorts, for both professionals and hobbyists, providing a single unified tool set to put Python's vast ecosystem and available tools to use, across a broad set of problem domains, for front-end, back-end, and full stack use. It's fast, powerful, hosted and curated when desired, open-source and freely configurable when desired, simple to learn, incredibly productive, and versatile. There really is no other software development solution which provides the same broad range of benefits.

(Please note that the author of this article is in no way affiliated with Anvil, and receives no reimbursement whatsoever from Anvil.works, for any materials or opinions presented on this web site).

3. Getting Started

3.1 The Anvil IDE and a Hello World app

To begin using Anvil, sign up for a free account at https://anvil.works/. Click the 'start building' link, then select 'New Blank App' -> 'Material Design'. This will bring you to the Anvil IDE (Integrated Development Environment):



Click the 'Run' button at the top center of the IDE, type in a name for your app when prompted ('MyApp', for example), and your app will open and execute in your browser (it doesn't currently do anything because we haven't created any design layout, or written any code yet). Click on the 'Publish this app' button, for a URL link that can be sent to others, so they can run your app in their browser. Click the 'Output' link to see any console interactions (data printed by code you write, error messages encountered while debugging code, etc.). Click the 'Stop' button to return to the IDE.



The left side 'App Browser' column of the IDE allows you to add and select client/server code modules, databases, services, and other components that make up your app. The right column contains a 'Toolbox' of user interface (UI) 'widgets' which you can drag and drop onto your app, and 'Property' settings such as width, height, text, and other layout values, as well as event handling functions and interactive choices which set how pieces of your app look and operate. The current design and code of your app is displayed in the center column of the IDE. You can switch back and forth between the visual 'Design' layout, and the 'Code' view of your app, in this center work area (labeled 'Form1' by default). Think of each form design you create as a separate screen/page in your application:



To begin making your first app, switch to Code view on the default 'Form1' page of your app. You'll see this default code:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Any code you write here will run when the form opens.

That may look a bit daunting at first, but don't sweat.

Any line that begins with '#' is just a human-readable 'comment' which is ignored by the Python interpreter, so those lines can be deleted from the code.

Notice the indented line beginning with 'def'. 'Def' is used to create a block of code in Python called a 'method' (or 'function'). The default __init__ method runs when your app starts. Type the following code into the bottom line of the __init__ method:

alert('Hello World!')

'Alert' is a function built into Anvil's Python API, which pops up a text message. Notice that the text 'argument' to be popped up by the alert function ('Hello World!'), is surrounded by quotes, and enclosed in parentheses. Be sure to type it in exactly as shown. Also, note that Python uses indentation to delimit blocks of code, so any code you add to the __init__ method definition must be indented using the same number of tabs (or spaces) as the default line already in the definition (i.e., indent it the same number of spaces as the existing line 'self.init_components(**properties)'). So, your edited code, with comments removed and lines indented properly, should now look like this:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    alert('Hello World!')



Click the Run button to see your app execute. Click 'Ok' on the alerted Hello World message box, and then click the Stop button to return to your code in the IDE.



3.2 Variables

Now edit the __init__ code definition again so it looks exactly like this:

def __init__(self, **properties):
  self.init_components(**properties)
  my_message = "This message is stored in the 'my_message' variable"
  alert(my_message)

In the code above, the label 'my_message' is defined as a variable, and assigned to some text, using the equal symbol. The text data alerted to the user, contained in parentheses (the 'argument' or 'parameter' of the alert function) is now replaced with the text assigned to the my_message variable. Run your app to see the my_message text alerted in your app.

To understand a bit more, variables are just labels, used to represent changeable data in a program: values which may have been entered by a user, for example, or returned by a database query, or read from a file, or gotten by the app from a network connection, etc. You create a variable by assigning a label to a value, with an equal sign: my_date = '01/15/2022'. When using variables to represent data in your programs, it's best to make up labels that clearly describe the data they represent, so that variable values are easily read and understood in your code. For example, assign a variable such as user_birthday = '05/10/1972' to represent a birth date, instead of b = '05/10/1972'. Descriptive variable labels make more sense when you read your code later - the purpose of 'user_birthday' is easier to understand than 'b'. As a matter of style, variables in the Python language typically use the underscore character to separate words in a variable label (this convention is not strictly required, but is considered a best practice).

Notice that the text assigned to the my_message variable above is surrounded by double quotes, and it contains single quotes. This can be inverted, so that text can be surrounded by single quotes, when it needs to contain double quotes. Triple quotes are used to contain multi-line text and quotes of any sort:

my_message = "This text contains 'single quotes'"

my_othermessage = 'This text contains "double quotes"'

my_bigmessage = '''
This text 
contains multiple lines, 
as well as 'single quotes'
and "double quotes"
'''

# variables can also be assigned to numbers, and other types of data:

my_number = 48

You'll use variables in Anvil and Python (and any other programming language) regularly, to represent data values of all types in your apps.

3.3 Adding a Button and Some Action

Now switch back from 'Code' to 'Design' view (at the top right of your app's 'Form1' screen, in the middle of the IDE), and drag a button widget from the toolbox on the right side of the screen, onto the form design:



Double-click the new button in your design layout, and Form1 in the IDE will switch back to the Code view, where you'll see that a new method definition has been automatically created:

def button_1_click(self, **event_args):
  """This method is called when the button is clicked"""
  pass

A 'method' in Python is simply a function definition (a named block of indented code), attached to some object. In this case, the 'button_1_click()' definition above is a function attached to the Form1 layout object - notice that it's indented within a block of code labeled 'class Form1()':



This method definition (block of code) will be executed every time a user of your app clicks the button widget. Change the code as follows (erase the 2 default indented lines, and replace it with the following code):

def button_1_click(self, **event_args
  my_message = 'You just clicked the button!'
  alert(my_message)

Let's also eliminate the alerted message that currently appears when the program starts, by erasing these 2 lines we previously added to the __init__ method:

# Delete these lines:

my_message = "this message is stored in the 'my_message' variable"
alert(my_message)

The code for Form1 should now look like this:

from ._anvil_designer import Form1Template
from anvil import *

    class Form1(Form1Template):

      def __init__(self, **properties):
        self.init_components(**properties)

      def button_1_click(self, **event_args):
        my_message = 'You just clicked the button!'
        alert(my_message)

Run the app, and the alert message will now pop up whenever you click the button:





Stop the running app, switch back to Design view on Form1, select the button widget (by just single-clicking on it this time), and scroll down the right side of your screen to the 'Properties' section below the toolbox. Change the default 'button_1' text to 'click me', and set its 'role' property to 'primary-color':



Notice that the text and appearance of the button has changed on your form. You can adjust these style choices however you prefer. Scroll down the properties all the way to the bottom and notice that the button's 'click' event has been automatically set to run the 'button_1_click' method that we edited earlier. You can change the name of that method here, to any other desired method definition, and click the blue '>>' button to be brought back into the code editor to make changes to that code definition, as desired:



For now, leave it as is.

3.4 Adding a Text Entry Field, Concatenation, and Saving Code Versions

Click the 'Save this version' link on the bottom left column of the IDE. Name this current version 'button' (or any other label you prefer) and click Save. You can now return to this version of your app at any point in the future.

Now select your button widget in the Form1 design view, and press the Delete key on your keyboard. This will delete the button from your app's graphic user interface ('GUI' or 'UI') visual layout. Switch to Code view and delete all the code that we earlier set to execute when the button was clicked:

# Delete this code:

def button_1_click(self, **event_args):
  my_message = 'You just clicked the button!'
  alert(my_message)

Switch back to design view and drag a 'TextBox' widget onto your Form1 UI:



Double-click the text box, and you'll be brought into the Code editor view again, where you'll find that a new 'text_box_1_pressed_enter' method definition has been automatically created (to be clear, double-clicking a widget in design view brings you to the code view of the automatically created method definition, which handles click events on that widget):



Edit the code for that method as follows:

def text_box_1_pressed_enter(self, **event_args):
  my_message = self.text_box_1.text
  alert('You typed: ' + my_message)

This code is important to understand. In the first indented line, the 'my_message' variable is now set to the '.text' property of the 'text_box_1' widget object. To be clear, in Anvil's Python code API, 'self.text_box_1.text' is how you refer to the text property of the text_box_1 design object on the current form ('self' refers to the current form). In the second indented line above, the alert function is used to display 2 'concatenated' (joined together) text values: a plus sign is used to join together the static (unchanging) text 'You typed: ', with the text value referred to by the 'my_message' variable. You'll regularly concatenate text and number values like this, in every sort of coding (this is common not just in Python, but in every programming language).

Run the app, type some text into the text box, and notice that the app echoes back whatever you've typed into the text box, in an alert message:



Notice that the IDE helps you enter code correctly with 'autocomplete' code suggestions, as you type. For example, when you type 'self.', Anvil's autocomplete feature suggests a variety of objects that are available to your code - 'text_box_1' is one of the objects you can select from the dropdown, for example. And after you select text_box_1, '.text' is one of the properties of that text box which can be autocompleted by a dropdown selection.



Autocompletion is a tremendously useful coding tool that helps you remember and correctly type pieces of code which are available to your app (variables, names of design objects, etc.).

3.5 Making Widgets Work Together Interactively, Built-In Responsive Design, and Errors

Now add a new button widget and a new label widget to your design view. You can position and resize widgets on your design screen, by dragging them around with your mouse, within the default column layout design. Try to make your design screen look like this:



Anvil will automatically resize and reorganize your layout for different screen sizes on desktop and mobile device displays, using best practices defined by 'Material Design' standards. You can of course adjust every detail of how this 'responsive design' works, if you want to learn some deeper details of HTML, CSS, and JavaScript. For the time being, you can simply allow Anvil to handle all that hard work for you, and accept the standard default layout options - they are typical of what users expect in modern web apps.

Let's also add a title to the app, by dragging a label widget to where is says 'Drop Title Here' (visible when your mouse hovers over the blue title bar):



Single-click the title label widget you just added (we just want to select it, not edit its click handler code, which would happen if we double-click it), scroll down the Properties column on the right hand side of the screen, and change the text property to 'Hello!'. Next, select the button widget you added earlier and change its text property to 'Say Hi'. Finally, select the label widget (next to the text box in the design shown above), change its text property to 'Name:', and its align property to 'right':



Now double-click the button widget, to switch back into Code view. As you've seen before, double-clicking any widget in the design view will create a new method that runs when the widget is activated (when users click a button, enter text into text boxes, etc.). Edit the new 'button_1_click' method definition as follows:

def button_1_click(self, **event_args):
  name = self.text_box_1.text
  alert('Hi ' + name + '!')

Read the code above and see if you can figure out what it does... ... It assigns the variable label 'name' to the text in the text_box_1 widget (remember, 'self.text_box_1.text' refers to the text property of that text box widget on the current page), and concatenates that text with 'Hi ', along with an explanation point at the end. That entire bit of concatenated text is alerted to the user. So, what this app does is allow the user to type their name into the text box, and then it says hello to the user when they click the button. Simple, right?

Now, since we no longer want the app do anything when the user enters text into the text box, you can delete the entire 'text_box_1_pressed_enter' method that we created earlier:

# Delete this code:

def text_box_1_pressed_enter(self, **event_args):
  my_message = self.text_box_1.text
  alert('You typed: ' + my_message)

Your code should now look like this:



Run it and see the results:



It works as expected, except we get an error warning in the output console: 'Warning: Form1.text_box_1_pressed_enter does not exist. Trying to set the 'pressed_enter' event handler of self.text_box_1 from Form1.'

Anvil will tell you when you've done something wrong in your code. It will tell you where the error is found, and potentially what needs to be done to fix it. In this case, we've removed the 'text_box_1_pressed_enter' method from the code, but if you select the the text_box_1 widget in your design, and look at its properties at the bottom of the right column, you'll see that 'text_box_1_pressed_enter' is still listed there as the method to run when a 'pressed_enter' event is registered for that widget.



You can fix this error by just deleting that method name for the 'pressed_enter' event. Instead, however, let's type in 'button_1_click' as the method to run for the text_box_1 'pressed_enter' event.



This will run the 'button_1_click' method if the user clicks enter after typing in their name. Since 'button_1_click' is the same method that runs when the user clicks a button (remember, that method was created automatically by double-clicking the button_1 widget - and you can see it listed in the click event property of the button widget), now the same thing will happen whether the user clicks the button or presses enter after typing in their name ... and we no longer receive an error warning in the console:



Notice that you can use the tab key on your keyboard to jump between selecting the text field and the button widget in your app UI ('user interface' - the screen layout). This is common practice, and is implemented automatically by Anvil. You can change this default behavior if you want to dig deeper into Anvil. We'll leave that default for now, but let's add one more feature...

After the alert message is displayed to the user, let's automatically erase the name that the user typed into the text box, and make the cursor jump back to the empty text field, so the user can type in another name without having to erase and reset the text box manually. Add these 2 lines to the 'button_1_click' method:

self.text_box_1.text = ''
self.text_box_1.focus()

This sets the '.text' property of the 'self.text_box_1' object (the text in the text box widget) to empty, and then focuses the cursor back into the text_box_1 widget.



Although not required, these additional few lines of code handle some common user expectations about how the app might work. When creating more complex UIs to be used by many people with a wide range of computing experience, it's helpful to make your app guide the user's experience as automatically as possible, so that the interface is easy/intuitive to interact with, and so that users enter information into the intended widgets, validate proper input, etc.

3.6 Publish Your App with One Click

To publish this app to the world, run the app and click 'Publish this app'. You'll see the following dialogue appear:



Copy the generated app link and send it to your friends in an email or text message - or publish it on social media, in a chat app, on a forum, on a web site, etc. Anyone with any modern computing device that has a modern web browser will be able to run your app instantly by clicking the link, without needing to install anything. Anvil does automatically offer users the option to install your app on their device, which provides the benefits of creating a link on the user's home screen (so it appears to run like any other native installed app), and also allows the user to run your app offline if they're not connected to the Internet. Installed Anvil apps can be configured so that they work offline, and then update saved data later, whenever an Internet connection becomes available. If users don't want to install your app, they can simply save the link in their browser favorites/bookmarks, or just copy/paste/click the link wherever they originally found it (in some text or email message, on some web page, etc.), whenever they want to use it in their browser.

You can create a shorter, more descriptive URL link by clicking 'Share via public link' in the publish dialogue. A default short link is provided, but if you have a paid Anvil account, you can type in any name for the app, followed by the extension '.anvil.app'. See the URL in the screenshot below - you can actually go to that URL now in any browser, and you'll see the app from this tutorial running live online.



3.6.1 A Few Optional Deployment Features

You can also embed your app into any web page, by copying the embed code provided in the publish dialogue. Simply paste these 2 given lines into any HTML, and your app will appear within an iframe on the page (something like the following code, except Anvil will provide code containing the URL to your specific app):

<script src="https://anvil.works/embed.js" async></script>
<iframe style="width:100%;" data-anvil-embed src="https://myhello.anvil.app"></iframe>

If you own your own web site domain(s), and have a paid Anvil account, you can also publish your app(s) directly to any of your web site URL(s). Just click 'Add custom domain' in the publish dialogue, and follow the instructions for updating the DNS info to point your chosen subdomain to your Anvil app (if you own a domain through Godaddy, for example, you'll just need to update this info in your Godaddy account). None of this is required - it just provides the option for a more professional branding experience which ties your app directly to your web site URL, with no hint of Anvil hosting shown in your app.

3.7 Finishing up With Hello World, Saving to GIT and Backing Up

Before we finish our first Hello World project, lets save the current version by clicking 'Save This Version' once again. Name this version 'hello user', and click 'View History'. You'll see the version we saved earlier, with the option to switch between and restore any version you've created.

You'll notice a 'Clone with GIT' button on the version popup, which provides instructions for copying the project into a GIT repository. Most modern code projects are stored in GIT repositories. You can learn how to use Github.com and other GIT tools elsewhere (that's slightly beyond the scope of this tutorial) - just be aware that Anvil makes GIT integration work automatically, with virtually no effort on your part.

Finally, click the gear icon at the top left hand App Browser column of the IDE. Select 'Share App', scroll all the way down to the bottom of this dialogue and click 'Download as a File'.





Download the '.yaml' file, rename it if you want, and save it anywhere on your hard drive, so that you have a backup copy of the project on your local computer. That single .yaml file contains every design layout element, property setting, database structure, and piece of code which has been created in your app. You can save it to a flash drive, send it by email, store it on a web server, reload it to another account, etc., and every bit of work you've done on the current version will be fully restored. This makes backing up and sharing complete Anvil projects simple.

Click the '<' button at the top left corner of the Anvil IDE (to the left of 'App Browser'), to close the 'MyApp' application we've been working with.



This will bring you to the 'My Apps' tab of the Anvil IDE. You can bookmark this IDE page in your browser at https://anvil.works/build#page:apps .

4. Off and Running With a Live Webcam Viewing App - DropDowns and URLMedia

At this point, you've seen enough of Anvil's functionality to begin making more interesting apps.

In the Anvil IDE, click 'Create App +' -> 'Material Design' to start building a new app. Click the 'Material Design 1' header, and give this new app the title 'Live Web Cam Viewer':



Click and drag an Image widget and a DropDown widget from the toolbar onto your design form:



Select the DropDown widget in your design view and scroll down to the 'items' property in the right hand column. Paste the following URLs into that items list, exactly as they appear below, with one URL per line. Each line item will appear as a selection in the DropDown selector widget:

https://cwwp2.dot.ca.gov/data/d2/cctv/image/pitriverbridge/pitriverbridge.jpg
https://cwwp2.dot.ca.gov/data/d3/cctv/image/hwy99atgarnerlane1/hwy99atgarnerlane1.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/johnsongrade/johnsongrade.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/perez/perez.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/mthebron/mthebron.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/eurekaway/eurekaway.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/sr70us395/sr70us395.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/bogard/bogard.jpg
https://cwwp2.dot.ca.gov/data/d2/cctv/image/eastriverside/eastriverside.jpg
https://cwwp2.dot.ca.gov/data/d10/cctv/image/sbi5sopeltierroad/sbi5sopeltierroad.jpg



Run the app and see how the DropDown widget allows the user to select one of the URLs:



Stop the app and double-click the DropDown widget. The code view will open to the automatically created 'drop_down_1_change' method (as you've seen, this automatically created method handles what the program does when a user interacts with the DropDown widget):



Erase the default lines of that method definition. By default, they simply contain a 'doc string' where you can explain what the method does, and 'pass', which is a placeholder that makes the method definition do nothing temporarily. Add these 2 lines to the definition:

my_media = self.drop_down_1.selected_value
self.image_1.source = URLMedia(my_media)



In the code above, the variable label 'my_media' is assigned to the value selected by the user, from the DropDown list. Then the 'source' image of the widget is assigned to the image data at that selected URL. The Anvil 'URLMedia' function handles the work of downloading the image data at a given URL and converting it to a format that can be displayed in your Anvil apps.

Run your app, and you'll see live images from any selected URL appear, in the image widget. These images are updated regularly by the owners of the cameras, so any time you run the app, you'll see actual current images from each of the cams:





That was pretty simple, right? In addition to the basic 'hello world' Anvil IDE functionality, all that was needed to learn how to make this app, was how to put line items into a DropDown widget, and how to use Anvil's 'URLMedia' API function to get image data from a URL.

You can see a live copy of this app running at http://camviewer.anvil.app

Publish the app, send the link to some friends, save the current version (and download a backup .yaml copy if desired), then close the app in the IDE and return to your account at https://anvil.works/build#page:apps by clicking the '<' icon in the top left of the IDE. This process should start getting a bit more natural as we create the next few app examples.

5. Coin Flip App - Using Python Libraries, Random and Time

Create a new app in the IDE and rename it 'Coin Flip'. Drag an image widget and a button widget onto the Form1 Design view, and change the 'text' property of button_1 to 'Flip':



Select the image_1 widget (single-click it), scroll down to its 'appearance' property, click 'more', and set its 'background' color property to '#c8c8c8'. You can type in this value directly, or use the color picker dialogue to select the gray color (or type in 200, 200, 200 as the RGB (red, green, blue) values):



Now double-click the button widget, to create and edit its 'button_1_click' method definition. As you've seen, this method is created automatically when you double-click a widget, and it handles what happens when a user interacts with the widget. Delete the 2 default lines of code, and replace them with this block of Python:

def button_1_click(self, **event_args):
  self.image_1.source = None
  coins = ['http://re-bol.com/heads.jpg', 'http://re-bol.com/tails.jpg']
  coin = random.choice(coins)
  time.sleep(1)
  self.image_1.source = URLMedia(coin)



There are a few new functions in the code above, along with some pieces that should be familiar at this point. The first line sets 'self.image_1.source' (the 'source' image property of the 'image_1' widget on the current form ('self')), to be 'None' (empty). Note that 'None' is a standard Python object, which needs to be capitalized.

self.image_1.source = None

The second line above sets the variable 'coins' to a list of URL values (these URLs point to 2 .jpg files containing head and tail images of a coin). Python lists are a primary data structure which you'll use regularly in most apps, to contain multiple values. Lists are enclosed by square brackets, and each item needs to be separated by a comma.

coins = ['http://re-bol.com/heads.jpg', 'http://re-bol.com/tails.jpg']

The third line sets the variable 'coin' to a random choice from the 'coins' list.

coin = random.choice(coins)

The fourth line of code above uses the 'sleep' function from Python's standard 'time' library (more about this library in a moment). The time function is passed an argument value of 1 second (in parentheses). This just helps the user to see a slight delay after clicking the button, so it's obvious that the coin flip image has been reloaded (remember, the image source was previously set to None, in line 1 above). Otherwise, the reloaded image could appear so quickly that the user may not notice the image refresh occur.

time.sleep(1)

Finally, the fifth line in the code above sets 'self.image_1.source' to be equal to 'URLMedia(coin)' (which is the image data loaded from the URL represented by the 'coin' variable, chosen randomly from the list earlier). You've already seen how Anvil's URLMedia() function loads image data from a URL, in the previous Live Web Cam Viewer section of this tutorial.

self.image_1.source = URLMedia(coin)

5.1 Using Functions in Python Libraries

Note that in Python, functions such as 'random' and 'time' are contained in 'libraries'. In order to use functions from any library, you must 'import' the library. Note the following import statements in the screen shot above. Be sure to include these 2 lines at the top of your Form1 code:

import random
import time

Import statements typically appear at the beginning of a code file. Learning which functions exist, in which libraries, and how to use those functions (what argument values to put in parentheses, what format(s) those data values need to be in, etc.) is a huge part of learning how to program in Python code. The use of libraries keeps Python applications from being internally bloated with every imaginable bit of unused function code. Only the pieces of code needed for the particular tasks used in a given app are included with import statements. You may have noticed that every new form in Anvil includes some default import statements which are part of the Anvil library:

from ._anvil_designer import Form1Template
from anvil import *

You'll see that the Anvil IDE automatically inserts necessary import statements whenever you add specific Anvil API functionality to your app (database, user management, Google service interactivity, etc.). You'll learn more about the Anvil library and API (the full set of functions and features available in Anvil), by following along in more tutorial examples, and eventually by just reading code examples, once you've got the basics down.

Be aware that Google is a tremendously useful tool to use when learning or remembering how to use 'standard library' functions in Python. Look up 'python random' in a Google search, for example, and you'll immediately find tutorials demonstrating the answers you need. You can typically learn exactly how to perform any common Python coding solution with a quick Google search.

Run the app and click the 'Flip' button a few times. You should see the image flip randomly. Notice that the grey background we set earlier matches the grey background in the coin images, so that the images' edges blend in smoothly with the screen layout:





You can see a live copy of this app running at http://coinflip.anvil.app

Publish your app, save this version (and backup its .yaml file to your local hard drive, if you want), then close this app in the IDE and get ready for more examples!

6. A 'Time Between' Dates App, Conditional Evaluations and Dates

Create a new app in the IDE and rename it 'Time Between'. Drag 2 DatePicker widgets and a Label widget onto the Form1 Design view:



Set the 'pick_time' property of both date widgets to true, by clicking the check box:



Now double-click the date_picker_2 widget, to edit its automatically created 'date_picker_2_change' method definition (as you've seen, this handles what happens when a user interacts with the widget). Delete the 2 default lines of code, and replace them with this 1 line of Python:

def date_picker_2_change(self, **event_args):
  self.label_1.text = self.date_picker_2.date - self.date_picker_1.date



As you probably can expect by now, the 'self.date_picker_2.date' refers the date property (the date selected by the user) of the date_picker_2 object (the second date picker widget) on the current form. Remember, when dealing with form code in Anvil, 'self' refers to design/code objects on the current form. The 'date' property in Anvil date picker widgets is stored in a format used by the 'date' function in the standard Python 'datetime' library, so to perform calculations upon those date values, we must import that function from Python's datetime library. Be sure to include this line at the top lines of your Form1 code:

from datetime import date

The first line in the method definition above sets the value of 'self.label_1.text' to an evaluation in which the selected date on the 1st date widget is subtracted from the selected date on the 2nd date widget. Hopefully, it makes sense now that what this does is display the difference between those 2 dates, in the text label.

Run the app, select some dates and times, and notice that the time difference is displayed every time you click on a date in the second date_picker widget.

Can you figure out how to make the calculation occur every time a user selects a date in either date picker? ... ... To do that, you need to set the 'change' event property of the 'date_picker_1' widget to also execute the 'date_picker_2_change' method (along with date_picker_2 having its click event set to that method):



Run the app now, and ... uh oh ... we get an error:

TypeError: unsupported operand type(s) for -: 'NoneType' and 'datetime'
at Form1, line 11



Notice that the error points out line 11, and mentions 'NoneType' and 'datetime' operands. You can click on the 'Form1, line 11' link in the Output error message, to be brought directly to that line of the Code view for Form1:

self.label_1.text = self.date_picker_2.date - self.date_picker_1.date

What's happening, is that after selecting a date from date_picker_1, the 'date_picker_2_change' method is fired, and it's now performing the specified date calculation, before the second date has been set. To fix this error, replace the existing line 11 with this code:

if self.date_picker_2.date is not None and self.date_picker_2.date is not None:
  self.label_1.text = self.date_picker_2.date - self.date_picker_1.date



What this new line of code does, is perform an 'if' evaluation. This 'conditional evaluation' checks to see if the '.date' properties of both date_picker widgets are not set to 'None'. If that's the case - i.e., only if the entire condition of compared values evaluates to True (so in this case, both dates have been set) - then the indented block of code (the code originally on line 11) is executed.

Notice that the 'if' evaluation condition line above ends with a colon (':'). This is the standard Python syntax for conditional evaluations: conditional evaluation lines end with a colon, and code which should be run when the condition evaluates to true, is indented. Conditional evaluations are a fundamental part of every programming language, used to handle every sort of logic needed in apps. Search Google for 'Python if', and you'll find numerous tutorials and examples demonstrating how to use 'elif' and 'else' clauses to perform different blocks of code, depending upon how the condition(s) evaluate, how to put conditional evaluations on a single line, etc. For future reference, the following code snippet demonstrates the basic features of Python's conditional evaluation syntax (notice that double equal signs (==) are used to check for equality):

x = 7
if x > 0 and x <= 5:
  print ('x is between 0 and 5')
elif x == 6:
  print ('x is 6')
elif x > 6:
  print ('x is greater than 6')
else:
  print ('x is less than zero')

# here are 2 common short 1-line formats:

if x == 6: print('x is 6')
print('x is 6' if x == 6 else 'x is not 6')

Now rerun your app with the code changes shown above, and you'll see it works as expected, without any errors:



Publish your application (and save/backup this version, if you want). You can see a live copy of this app running at http://timebetween.anvil.app

7. Math Test - Eval(), Str(), Custom Function Definitions, and More Error Handling

Create a new app in the IDE and rename it 'Math Test'. Drag 4 label widgets, 1 text_box, 1 drop_down, and 1 button onto Form1 like this:





Set the 'items' property of the drop_down widget to the math operators '+ - * /' (put one item on each line):



Switch to Code view on Form1, and edit it like this:

from ._anvil_designer import Form1Template
from anvil import *
import random 

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.label_2.text = str(random.randint(0,9))
    self.label_4.text = str(random.randint(0,9))
    self.drop_down_1.selected_value = random.choice(['+','-','*','/'])

  def button_1_click(self, **event_args):
    answertext = (
      self.label_2.text + " " + 
      self.drop_down_1.selected_value + " " + 
      self.label_4.text
    )
    answer = str(eval(answertext))
    if self.text_box_1.text == answer:
      alert('Correct')
    else:
      alert('Incorrect')

Let's take a look through what that code does. All the default parts you've seen before, are there: some import statements for the Anvil library, an __init__ method, and a 'button_1_click' method.

Since we're going to generate some random numbers, Python's 'random' library needs to be imported:

import random

The app will display random integers between 0 and 9 in each label widget, and a random math operator symbol in the drop_down list. The 'randint()' function from the 'random' library creates the numbers we need. Note the Python 'str()' function, which converts the random numbers to string (character) values, that get displayed in the label widgets (if your labels are named something other than label_2 and label_4, that's fine, just edit the code to match the labels on your form). The 'choice()' function from the 'random' library chooses 1 item from the list of math operators, to select from the drop_down widget:

self.label_2.text = str(random.randint(0,9))
self.label_4.text = str(random.randint(0,9))
self.drop_down_1.selected_value = random.choice(['+','-','*','/'])

The user of this app will enter an answer to the random math expression that's been displayed by the code above. The following code in the 'button_1_click' method evaluates whether that answer matches the displayed expression's solution. First, an 'answertext' variable is set to a string of characters, concatenated from the random numbers and the random operator displayed on the form (notice that the concatenated values are enclosed in parentheses, so that they can be split across multiple lines of code):

answertext = (
  self.label_2.text + ' ' + 
  self.drop_down_1.selected_value + ' '  + 
  self.label_4.text
)

The next line uses the 'eval' function to evaluate the concatenated string of characters above, as if it's Python code. For example, if the text stored in the 'answertext' variable is '6 + 2', the 'eval' function returns the number 8. A string representation of this number gets stored in the 'answer' variable:

answer = str(eval(answertext))

It's important to understand that in Python, the number 63 is different from the quoted string '63'. The quoted '63' value is called a 'string', or a string of characters. You can't perform math upon string values - applying the plus symbol concatenates string values, rather than performing a math operation:

63  +  12  =   75
'63' + '12' =  '6312'

It's common in every programming language to 'cast' values from one type to another, especially when performing computations upon data in graphic user interfaces. In this case, the data displayed in label widgets are string values, not numbers, so those numbers need to be cast back and forth between number and text values. Keep this in mind wherever debugging unexpected errors in your code. It's a common coding pitfall in every programming language.

Next in the code above, an 'if' conditional evaluation is used to compare the number entered by the user, to the 'answer' variable. If the answer matches, a 'Correct!' message is alerted. Otherwise, an 'Incorrect' message is alerted:

if self.text_box_1.text == answer:
  alert('Correct!')
else:
  alert('Incorrect')

Run the app a few times to see it in action. At the moment, the app only provides a single math test question on each run. It's clear that we need to repeat the process of generating random math expressions and checking user answers. To do that, we still want the random number generation code to run when the app starts, but we also want that code to run again after each time the user submits an answer (so that the app repeatedly displays and evaluates answers to new randomly generated test questions). To do that, we'll 'refactor' (reorganize) the code a bit. Take our code from the __init__ method and create a new method definition containing just that code. We'll name the new method 'reset_operands'. Notice that this new method definition requires 'self' as its first argument in parentheses. This is a convention of the Python object-oriented system:

def reset_operands(self):
  self.label_2.text = str(random.randint(0,9))
  self.label_4.text = str(random.randint(0,9))
  self.drop_down_1.selected_value = random.choice(['+','-','*','/'])

While we're at it, let's make the reset_operands method also erase the answer the user has entered, from text_box_1, and focus the cursor back to text_box_1. This is just a convenience, so the user doesn't have to manually erase and reset the entry field to answer each new test question (you saw this in a previous tutorial example):

self.text_box_1.text = ''
self.text_box_1.focus()

Now we can execute the reset_operands() method above, in the __init__ method, and also at the end of the button_1_click() method. Notice that the reset_operands() method is referred to as 'self.reset_operands(), because it exists as an object on the current design form (in the same way that 'self' is used to refer to widgets on the current form). The complete program now looks like this:

from ._anvil_designer import Form1Template
from anvil import *
import random 

class Form1(Form1Template):

  def reset_operands(self):
    self.label_2.text = str(random.randint(0,9))
    self.label_4.text = str(random.randint(0,9))
    self.drop_down_1.selected_value = random.choice(['+','-','*','/'])    
    self.text_box_1.text = ''
    self.text_box_1.focus()

  def __init__(self, **properties):
    self.init_components(**properties)
    self.reset_operands()

  def button_1_click(self, **event_args):
    answertext = (
      self.label_2.text + ' ' + 
      self.drop_down_1.selected_value + ' ' + 
      self.label_4.text
    )
    answer = str(eval(answertext))
    if self.text_box_1.text == answer:
      alert('Correct!')
    else:
      alert('Incorrect')
    self.reset_operands()

Run the app to witness all this working math test code in action:





After testing the app a bit, you may realize that a few more features need to be added. For example, if the randomly chosen math operation is division ('/') and the second random number is zero, we'll get a divide-by-zero math error in the console. To fix that potential error, add the following code to the reset_operands method:

if (self.drop_down_1.selected_value == '/') and (self.label_4.text == '0'):
  self.reset_operands()

The code above uses an 'if' conditional evaluation to check if the divide-by-zero situation has occurred, and if so, it simply re-runs the reset_operands method. A method calling itself like this is called a 'recursive' operation.

By testing the app a bit more, you may run into another unexpected situation. If you type a fractional value slightly differently than Python represents that value internally (for example, .25 instead of 0.25), then you'll get an 'incorrect' response, even though the numbers represented are technically the same. To fix that, use the same str() and eval() functions upon the values that will be compared (both the randomly generated expression, and the value submitted by the user), before the if evaluation:

# these 2 lines massage the values into the same type:

answer = str(eval(answertext))             
submitted = str(eval(self.text_box_1.text))

if submitted == answer:
  alert('Correct!')
else:
  alert('Incorrect')

That fixes the problem, but has created a new potential problem. Now, because we're using the eval() function upon the value the user has typed in, all the user needs to do is type in the random question expression (i.e., something like '5 * 7'), and they'll get a 'correct' response every time! We can fix that potential situation by adjusting the 'if' evaluation to respond to it (BTW, notice the use of double quotes containing a single quote in the alerted text):

if self.text_box_1.text == answertext:
  alert("Incorrect, you can't type the problem as the solution")
elif submitted == answer:
  alert('Correct!')
else:
  alert('Incorrect')

Let's add one additional feature... As you've seen before, it's a nice convenience to allow the user to either click the button, or to simply press enter after typing in their answer. As you've seen, all we need to do is set the 'button_1 click' method to run button_1's 'click' event, and also for text_box_1's 'pressed_enter' event:





Here's the final code:

from ._anvil_designer import Form1Template
from anvil import *
import random 

class Form1(Form1Template):

  def reset_operands(self):
    self.label_2.text = str(random.randint(0,9))
    self.label_4.text = str(random.randint(0,9))
    self.drop_down_1.selected_value = random.choice(['+','-','*','/'])    
    self.text_box_1.text = ''
    self.text_box_1.focus()
    if (self.drop_down_1.selected_value == '/') and (self.label_4.text == '0'):
      self.reset_operands()

  def __init__(self, **properties):
    self.init_components(**properties)
    self.reset_operands()

  def button_1_click(self, **event_args):
    answertext = (
      self.label_2.text + ' ' + 
      self.drop_down_1.selected_value + ' ' + 
      self.label_4.text
    )
    answer = str(eval(answertext))
    submitted = str(eval(self.text_box_1.text))
    if self.text_box_1.text == answertext:
      alert("Incorrect, you can't type the problem as the solution")
    elif submitted == answer:
      alert('Correct!')
    else:
      alert('Incorrect')   
    self.reset_operands()

Run the app to see all the new changes in effect:



Publish, save, and close the app in the IDE when you're done working. You can see a live copy of this complete app running at http://mathtest.anvil.app

8. A Multi-Page Information Web Site App - Navigation Links

Every app so far in this tutorial has required only a single visual form layout design. When creating larger apps - especially informational web sites that deliver multiple pages of content (for example, marketing web sites which display information about business offerings) - it's typical to provide 'navigation' links that users click to switch between 'pages' (form designs). Create a new app in the Anvil IDE and name it 'Multi-Page Site'. Drag a label widget to the title bar, and change its text property to 'Home':



Next, in the left column app browser, click the '+' symbol (next to 'Client Code'), and select 'Add Form' -> 'Standard Page':





Repeat this process again, so that you have a total of 3 forms in the 'Client Code' section of the app browser. Rename forms 1, 2, and 3 to 'Home', 'Contact', and 'About':




s

Drag labels onto the title headers of the Contact and About forms, and set the 'text' property for each page (just like we've already done for the Home page):





So far there's not much new here - you've seen most of these tasks in the very first Hello World example in this tutorial. All that's left to do is link the forms together, so a user can navigate between the page views. To do this, drag a 'ColumnPanel' from the toolbar onto the design screen of the 'Home' form. You'll see the layout pop up instructions 'to add a sidebar, drop a ColumnPanel here':



Now drag 2 'Link' widgets onto the ColumnPanel:



Double-click the link_1 widget to create a link_1_click method definition, and add this 1 line of code (notice how autocomplete helps you write this code):

open_form('About')



Hopefully it's clear that this code will open the 'About' form when a user clicks the link. Now double-click the link_2 widget and have it open the Contact page:

open_form('Contact')



Change the text property of both link widgets, so they each display text that properly describes the page they link to. Also notice that you can choose an icon property for the link:



Run the app, and click the links to switch to the Contact and About pages.





Notice that when you visit either the Contact or the About page, there's currently no way to click back to the home page. To remedy this situation, let's add a link back to the Home page, directly in the title bar at the top of the page (instead of adding a sidebar, as we did earlier). Set the text and icon properties just as we did earlier, then double-click the link and put open_form('Home') in the link_1_click definition for each link widget:







Note that you can choose to add links wherever you want on a page, even among scattered content elements. It's best practice, however, to put them either in the titlebar or a sidebar, as you've seen here.

Also note the lightning bolt icon next to the Home form. This indicates that the Home form is the first page that will be shown to users when the app starts:



You can choose to set any form as the startup page:



All that's left to finish the site is to add whatever information content is needed for each page. For most pages, you'll add at least a few Image and 'RichText' widgets to fill in any desired text. HTML and Markdown code can be used in RichText widgets, if you know how to use it:







Try running this app on a phone, tablet, and desktop PC. You'll notice that Anvil automatically adjusts the navigation bar and form layout for different sized screens, using typical 'responsive design' best practices.

Save a version of your app, publish, then close it in the IDE. You can see a live copy of this app running at http://multipage.anvil.app

9. A Simpler Way to Navigate Multi-Page Apps

In this example, we'll build another multi-page web site using an ever simpler method of navigation. In a similar way we did in the previous section, create a new app and add 3 few new forms named 'Home', 'About', and 'Contact'. Only this time, instead of 'Standard Page', select 'Blank Panel' as the look of the new pages:



As we did in the previous section, create a sidebar ColumnPanel with links for the Home, About, and Contact pages:



Now edit the Form1 code as follows:

from ._anvil_designer import Form1Template
from anvil import *
from ..Contact import Contact
from ..About import About
from ..Home import Home

    class Form1(Form1Template):

      def __init__(self, **properties):
        self.init_components(**properties)
        self.link_1_click()

      def link_1_click(self, **event_args):
        self.content_panel.clear()
        self.content_panel.add_component(Home())

      def link_2_click(self, **event_args):
        self.content_panel.clear()
        self.content_panel.add_component(Contact())

      def link_3_click(self, **event_args):
        self.content_panel.clear()
        self.content_panel.add_component(About())

Make sure the click event for each link widget in the design view points to the correct '_click' method above.

And that's it. What the code above does is replace only the content panel on Form1 with the blank panel forms we created earlier, whenever the user clicks a link. Notice that the 'link_1_click' is also executed in the __init__ function, so that the Home content panel is loaded when the app starts.

Using content_panels, the majority of a form's design stays the same, including the header and the sidebar navigation ColumnPanel - just the main content in the center of the page is replaced. This saves many steps needed to add navigation links to separate full form designs, and is a great way to add many sub-pages quickly to a site.

Consider how most web sites work - the top and/or side navigation panels often stay the same, and only the inner content changes when you click a link. In many cases, the content_panel technique above will often be preferable to designing and linking full pages. By combining both ways of linking content, it's possible to create virtually any sort of common page navigation design. For example, you may want to create several different full form designs to represent separate categories or themed groups of content pages, and then switch between content panels within each form design category.

When you have more than 1 full form in your site design, you can replace 'self.' with 'get_open_form()', to switch between content_panels on different forms:

get_open_form().content_panel.clear()
get_open_form().content_panel.add_component(About())

Run the app to see this navigation technique in action:



Save a version of your app, publish, and close it in the IDE. You can see a live copy of this app running at http://panelnavigation.anvil.app

10. Chat App - Anvil's Built-in Database System

So far, every app in this tutorial has worked entirely on the 'front-end'. In each example, all the computations have occurred, and all the resources have existed, only in the user's browser. Many useful web apps, however, handle some sort of multi-user data, and/or make use of resources and computations which occur on centralized server machines. For example, if you want to manage login credentials and provide secure access to specific pieces of information stored in an online account, of if you want to process credit cards, or upload images that can be seen by other users of a web site, etc., you'll need to save data from users' browsers, to a hard drive on the web site server machine. Such 'back-end' server functionality also enables additional capabilities, such as feeding data through computation systems which can only be run on centrally connected systems where larger software libraries, more powerful hardware, and more complex software systems can be installed.

10.1 Using the Database

One of the most important pieces of back-end tooling is a 'database' (information storage) system. Anvil has a built-in Relational Database Management System ('RDBMS'), built upon the very powerful and popular PostgreSQL DB, with an additional visual 'table' (column/row) builder/editor. Information in Anvil's database can be entered, retrieved, updated, searched, sorted, and otherwise manipulated using nothing but Python (instead of needing to use 'SQL' query language). It's also possible for Anvil apps to connect with any other external database system supported by Python (SQL and noSQL). We'll do that in a later app example, but for now, Anvil will make database access as easy as child's play.

To create a database table to store information, create a new app named 'Chat', click the '+' symbol next to 'Services' in the left hand app browser column of the Anvil IDE, and select the 'Data Tables' service:



Now click 'Data Tables' -> Add a Table -> Create New Table:



Name the new table 'chat' and create 3 new columns named 'texts', 'time', and 'name':



As you can see in the image above, when you click the '+' symbol to add a new column, you can choose the type of data which will be stored in that column (text, number, date, media, etc.). Set the 'texts' and 'name' columns to contain text information, and the 'time' column to contain date and time information, exactly as you see in the screen shot. These columns will fill up with data when people use the app:



Finally, set the 'permission' for forms to 'can search, edit, and delete':



IMPORTANT: This setting is insecure. It provides direct access to the database via front end code, and potentially allows any knowledgeable user of the app to gain access to the database and make unwanted changes to your data. We'll address this security setting in depth in later examples, but use it here just for simplicity while introducing database functionality. This setting should only be used for personal or in-house production apps, or in other situations when users of the application can be trusted not to make malicious changes to sensitive or critical information contained in the database:

Now add a TextBox, TextArea, Button, and 'RepeatingPanel' widget to the Form1 Layout, as shown below:



Set the row_background container property of the TextArea to the color '#f4f5f6' (this just helps the data entry widgets stand out visually to the user):



In the Toolbox on the right, click 'See More Components', and drag a 'Timer' component onto the Form1 design layout:



Set the 'interval' property of the timer to 20 seconds:



Now select the RepeatingPanel widget and add 2 labels and 1 TextArea widget to the 'Item1 Template' layout:



Uncheck the 'Interaction' property of the TextArea widget in the Item1 Template (this keeps users from being able to edit the text in this display widget):



The RepeatingPanel widget above will fill up vertically to display selected rows of data from the database, using the template layout on each row. In other words, each row will look like the widget layout in the Item1 Template, but will display a different row of info from the database - the number of repeated rows will expand and contract to display as many rows of data as needed.

Select the leftmost label in the template layout, and click '+Add', to add a 'Data Binding' property of self.item['name'], for that widget:



Add a similar data binding of self.item['texts'] to the TextArea widget:



Add a data binding of self.item['time'] to the rightmost label widget:



Notice that the self.item['name'], self.item['texts'], and self.item['time'] data bindings above correspond to the 3 columns of the chat table we created in the database service ('texts', 'time', and 'name'). So, when the repeating panel is refreshed, it will display selected rows of information from those columns in the database, according to those data bindings. To make this work, add the following code to Form1:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
from datetime import datetime

    class Form1(Form1Template):
def update_chats(self):
  self.repeating_panel_1.items = app_tables.chat.search(
    tables.order_by("time", ascending=False)
  )

def __init__(self, **properties):
  self.init_components(**properties)
  self.update_chats()

def button_1_click(self, **event_args):
  if self.text_area_1.text != "":
    app_tables.chat.add_row(
      texts=self.text_area_1.text, 
      time=datetime.now(),
      name=self.text_box_1.text
    )
    self.text_area_1.text = ""
    self.text_area_1.focus()
    self.update_chats()

def timer_1_tick(self, **event_args):
  self.update_chats()

Let's take a look at every line in the code above, to see exactly how it works.

First, you'll notice that whenever you add a database service to your app, the Anvil IDE automatically imports some additional libraries:

import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

And since we're going to use the 'now()' function from the standard 'datetime' Python library, that library also needs to get imported. You need to manually include imports yourself, for any functions from the standard Python library, or for any third party libraries you want to use:

from datetime import datetime

Most of the work in this code happens in the button_1_click method definition:

def button_1_click(self, **event_args):
  if self.text_area_1.text != "":
    app_tables.chat.add_row(
      texts=self.text_area_1.text, 
      time=datetime.now(),
      name=self.text_box_1.text
    )
    self.text_area_1.text = ""
    self.text_area_1.focus()
    self.update_chats()

In the code above, when the button is clicked by the user, if text_area_1 is not empty, then a row is added to the database. Notice that the 'if' evaluation checks to ensure the '.text' property of text_area_1 is not equal to "" (!= is the Python operator for 'not equal'). The app_tables.chat.add_row() method adds a row to the chat table in the database. The three arguments to that method set the 'texts' field to the text contained in text_area_1, the 'time' field to the current time (datetime.now()), and the 'name' field to the text in text_box_1. Take a close look at those 3 lines of code in the add_row() method argument (inside the parentheses after the add_row method) - you'll do that sort of thing every time you add data to a database table. The next 2 lines simply clear the text that the user has typed into text_area_1, and refocus the cursor into the text_area_1 widget (you've seen this routine several times in previous examples). Most of this code should be fairly recognizable at this point: setting variable values to data entered into widgets by the user, using the 'if' conditional evaluation, etc. The only totally new method is the app_tables.chat.add_row() method.

The last line of code above runs the update_chats() method, which has been defined as follows:

def update_chats(self):
  self.repeating_panel_1.items = app_tables.chat.search(
    tables.order_by("time", ascending=False)
  )

The code in the definition above sets the 'items' variable in the repeating_panel_1 widget to be some data returned from the database. The app_tables.chat.search() method searches the database for rows that match a 'query' argument. If the query argument is left blank (i.e., if nothing is put in parentheses), then the search simply returns every row in the table. In the argument above, all the rows are returned, sorted in descending order, according to the 'time' column. Take a close look again through the code above to get used to the basic syntax of how the 'search()' method works, and how the repeating panel items are updated, keeping in mind that each widget of the repeating panel template was bound earlier to a field in each row of the database query results. You'll do that sort of thing every time you want to get data from a database table. It may seem a bit complicated right now, the first time you see it, but this system is much much simpler to use and easier to implement than typical SQL database queries, using other traditional web development technologies. You'll get used to these few design/code patterns very quickly as we use them in more examples. Just follow along and let it sink in for the moment - it really becomes quite quick to implement, and extraordinarily powerful/versatile to use, once you've gotten used to the routine. You'll use this sort of database setup over and over again in all sorts of applications, whenever you need to save data (this happens in virtually every type of app imaginable).

Notice that the update_chats() method above gets executed in the __init__ method definition, so all chat messages are displayed when the program starts. The update_chats() method is also run at the end of the button_1_click definition, so the message display is updated whenever the user enters a new message. And finally, the update_chats() method is also executed in the timer_1_tick definition, so the message display is updated periodically, whenever a timer 'tick' event occurs (we set that property earlier to 20 seconds - you can change that to update however often you'd like, but be aware that a faster value uses more Internet bandwidth on the user's device to perform refreshes more often).

Run the app, to see all this database goodness in action. You can enter your name and as many chat messages as you want, and other users can do the same. Everyone running the app will see the group chat messages update at the rate set in the timer click property, and whenever they enter a new message.



You can run this app live at http://mychat.anvil.app

11. Cash Register

This next app is a minimum viable point-of-sale cash register program that allows the user to enter 'item' and 'price' values to a checkout list. Subtotal, tax, and total values are calculated, and the transaction is saved to a database.

To begin, create a new app named 'Cash Register', and add TextBox, Label, RepeatingPanel, Spacer and Button widgets to the Form1 Layout, as shown below:



As you've seen, the RepeatingPanel widget above will fill up vertically to display rows of data, using the template layout on each row. In this case, rows of item and price values will be added by the user. Add a TextBox widget with the 'type' property set to 'text', on the left side of the template layout, and add a 'Data Binding' property of self.item['item'], for that widget:



Add a TextBox with the 'type' property set to 'number' on the right side of the template layout, and add a data binding of self.item['price'] to that widget:



Notice that the self.item['item'] and self.item['price'] data bindings above correspond to the 2 TextBox inputs at the top of the design layout. So, when the repeating panel is refreshed, it will display rows of information added from those TextBoxes, according to those data bindings. To make this work, add the following code to Form1 - note that the name of the second Textbox has been set to 'text_box_price', so this is the automatically created method which is executed whenever the enter key is pressed in that TextBox:

def text_box_price_pressed_enter(self, **event_args):
  temp = self.repeating_panel_1.items
  temp = [] if temp is None else temp
  temp.append ({'item': self.text_box_item.text, 'price': self.text_box_price.text})
  self.repeating_panel_1.items = temp
  subtotal = 0
  for i in temp: subtotal += (i['price'])
  self.text_box_subtotal.text = subtotal
  tax = subtotal * .06
  self.text_box_tax.text = tax
  self.text_box_total.text = subtotal + tax
  self.text_box_price.text = ''
  self.text_box_item.text = ''
  self.text_box_item.focus()

Let's take a look at every line in the code above, to see exactly how it works.

In the first line, the data 'items' in repeating_panel_1 are assigned to the variable 'temp'.

In the next line, a special form of Python 'if' evaluation is used to set the value of the 'temp' variable to an empty list, if the current value of the variable is initially set to 'None'. This provides us a list datatype to add more values to. Otherwise, if the value of 'temp' is not None (i.e., a list of items already exists), then the value is simply reset to the current value of 'temp' (i.e., it isn't changed).

Next, the 'temp' variable is appended with a new set of 2 'dictionary' (key: value) pairs. The first appended key:value pair consists of the key label 'item', and the value which the user has typed into the text_box_item field. The second key:value pair consists of the key label 'price', and the value which the user has typed into the text_box_price field.

Next, the '.items' property of repeating_panel_1 (the rows of data it displays), is set back to the updated values in the 'temp' variable.

The next 6 lines compute and display the subtotal, tax, and total values updated in the cash register layout.

First, the variable 'subtotal' is set to zero.

Then a 'for loop' is used to go through each item in the 'temp' list, and add each 'price' value to the 'subtotal' variable. You'll see 'for' loops covered in depth later in this tutorial. For now, just understand that loops are used to do something to/with each item in a list - in this case, to add up all the price values on each individual row of the repeating panel, to create the subtotal calculation.

Next, a 'tax' variable is set to the value of the 'subtotal' variable, times .06. The '.text' property of the 'text_box_tax' widget is updated to display this computed 'tax' variable.

Finally, the values in the 'subtotal' and 'tax' variables are added together, and the 'text' property of the 'text_box_total' widget is updated to display the results.

The last 3 lines of the code above simply clear the 'text_box_price' and 'text_box_item' fields in the display, and focus the cursor back into the 'text_box_item' field (this is just a convenience for the user, as you've seen in earlier tutorial examples).

Run the app, and you'll see you've got a working cash register app which allows users to neatly enter repeated item and price values, and which calculates/displays subtotal, tax, and total values.



11.1 Generating Sales Reports

If you lived in the dark ages, you could simply print 2 paper copies of the page display, give 1 to the customer, and save 1 one for your sales records... but this would require lots of paper, and lots of manual computations with a calculator, whenever you wanted a summary of total sales for a given time period. Instead, let's use the Anvil database to store sales records, and perform some computations to automatically provide sales summaries for any given time period.

To add a database, click the '+' symbol next to 'Services' and add the 'Data Tables' service (just as in the previous section of this tutorial):



Click 'Data Tables' -> Add a Table -> Create New Table:



Create a new table named 'sales' with 2 new columns named 'receipt' and 'datetime'. Set the 'receipt' column data type to 'simple object', and the 'datetime' column to 'date and time'. Set the 'permission' for forms to 'can search, edit, and delete' (remember, this is insecure for anything but in-house apps):



Now double-click the 'button_1' widget in the Form1 design and add this code to the 'button_1_click' method:

def button_1_click(self, **event_args):
  app_tables.sales.add_row(
    receipt=self.repeating_panel_1.items, 
    datetime=datetime.now()
  )
  alert('Saved!')
  self.repeating_panel_1.items = []
  self.text_box_subtotal.text = ''
  self.text_box_tax.text = ''
  self.text_box_total.text = ''
  self.text_box_item.focus()

Let's go through the code above, line by line. You should recognize the '.add_row' function from the previous section of the tutorial. In the code above, it adds a new row to the 'sales' data table. The 'receipt' column value of the added row is set to contain all the data in repeating_panel_1 (the '.items' property of the repeating panel, or all the rows of 'item' and 'price' values entered by the user). The 'datatime' column value is set to the current date and time.

After the row of data has been added to the database, the user is alerted with a 'Saved!' message, then the subtotal, tax, and subtotal TextBoxes are cleared (set to ''), and the cursor is conveniently focused back on the 'text_box_item' field, so that the user can begin entering a new sale.

Remember since the 'now()' function from the Python 'datetime' library is used, that library needs to get imported at the beginning of the program code (you've seen this in previous tutorial examples):

from datetime import datetime





Run the app, and now you'll see that each sale is entered into the database, whenever the user clicks the 'Save' button.

To create a sales report, let's add a new link to the header bar, with the text 'Sales Report':





Double click the link widget and add this code to the automatically generated 'link_1_click' method definition:

open_form('Report')

Now add a new client form to the app browser, and name it 'Report' (just as you did in the section of the tutorial about navigation). Add 2 labels, 2 date pickers, and a button to the Report design layout:



Be sure to set the 'pick_time' property of both date pickers to 'True' by clicking the check box:



Double-click the button widget and add this code to the button_1_click method:

sales_total = 0
for row in app_tables.sales.search():
  if (
      row['datetime'] >= self.date_picker_1.date and
      row['datetime'] <= self.date_picker_2.date
    ):
    for item in row['receipt']:
     sales_total += item['price']
alert(sales_total)
open_form('Form1')

Let's take a look at what each line above does. In the first line, the variable 'sales_total' is set to zero.

Then a 'for loop' is used to go through each row in the 'sales' table of the database (remember, loops do something to/with each item in a list). Within this loop (indented), if the datetime value on any individual row is greater than or equal to the date picked by the user in date_picker_1, and less than or equal to the date picked by the user in date_picker_2, then another loop is run upon each of the receipt items in the row. Within this loop (further indented), the 'sales_total' variable is updated with each additional price of every item in the row.

The value of the final 'sales_total' variable is alerted, and the user is returned to the Form1 layout. We've now got a working report system:





We could add many more features to this app. For example, it's important to write some code to ensure users don't get an error if they click the 'generate report' button before selecting dates on the sales report screen (you've seen examples of this sort of code in the previous 'time between' example of this tutorial). Perhaps it would be useful to save the name of the sales representative who entered each sale, and provide some 'if' evaluations in the sales report to provide totals for each cashier. You could also, for example, provide a drop_down item selector so the cashier could simply select between a list of pre-defined items, instead of having to type in item names. And perhaps it would be helpful to add some 'if' logic to perform automatic price reduction calculations upon specific items which are purchased on given sales dates. Pick one simple such feature and see if you can figure out how to code it!

You can run this app live at http://register.anvil.app

12. Birthday List App - CRUD with Server Functions and Multiuser Account Management

We're going to move these examples along a bit faster now. Descriptions of code and design elements you've seen already will be kept to a minimum.

Create a new app called 'Birthdays'. Add a data table service to the app, create a table named 'birthdays', add 3 columns to the table: 'name' (text), 'date' (date), and 'gift' (text), and set the form permissions of the table to 'No Access' (this setting is important), as follows:



Create a second table named 'gifts', add 1 column named 'gift_choice' (text), and set the form permissions of this table to 'Can Search Table'. Populate the fields of the gift_choice column with the values: 'just a call', 'dinner', 'amazon gift certificate', 'go shopping with them', 'card with money', 'card without money', and 'none', as follows:



Add button, label, text_box, date_picker, drop_down, link, and repeating_panel widgets to the Form1 design, as follows:



Select the repeating_panel_1 widget and edit the layout of its repeating ItemTemplate1, so it contains 3 label widgets, then bind their data properties to self.item['name'], self.item['date'], and self.item['gift'], as follows (following the same patterns as in the previous app example):



Add a second form named 'Info' to the Client Code section of the IDE's App Browser (as we did in the navigation example apps). Drag label, link, and rich_text widgets onto the design of this form, as follows:



Edit the Info form code as follows:

from ._anvil_designer import InfoTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

class Info(InfoTemplate):
  def __init__(self, **properties):
    self.init_components(**properties)

  def link_1_click(self, **event_args):
    open_form('Form1')

The code above should look familiar to you: there are some normal imports of the Anvil library, which the IDE has inserted automatically. The __init__ method doesn't do anything except initialize components on the form (this is just the default code which Anvil inserts in the __init__ method). The 'link_1_click' method navigates back to the 'Form1' page, as you've seen in the earlier navigation examples (that's the only line of code that needed to be programmed on this form).

12.1 Our First Server Code

Now we're going to add our first Server code to an app. Click the '+' symbol to the right of 'Server Code' in the App Browser, and select 'Add Server Module':



Replace the default server module example code with the following:

import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server

@anvil.server.callable
def add_info(name, date, gift):
  app_tables.birthdays.add_row(
    name=name, 
    date=date,
    gift=gift,
  )

@anvil.server.callable
def get_info():
  return app_tables.birthdays.search(
    tables.order_by("date", ascending=True),
  )

Let's take a look a the code above, piece by piece. First, there are some Anvil library imports, which the IDE inserts for you.

Next, there is an 'add_info' function definition, which accepts 3 argument values (name, date, gift). The add_row method in this definition adds each of those argument values to the name, date, and gift columns of a new row in the birthdays table of database. This code pattern should look familiar to examples in the previous section of the tutorial.

Finally, the 'get_info' function definition returns rows of data from the database, retrieved via the 'search' method, sorted in ascending order, by the date column. This code pattern should also be familiar from the previous section of the tutorial.

To understand how we're going to use this code, it's important to understand that any code in a server module runs on the web server computer, instead of in a user's browser. Code for the add_info() and get_info() functions above is not available to the user's browser at any point (users of the app can't edit that code). Running functions on the server improves security, because unlike front-end code which can be manipulated by a knowledgeable user with nefarious intent, the server code can't be manipulated by users. It's important to understand that anyone with strong knowledge of how browsers work can potentially change the front end code of any web page that appears in their browser, and potentially have that page submit any info they want back to the server. If any code on a web page has direct access to columns of data in your database (as we allowed in the previous app example), then any knowledgeable user can potentially change any fields of data in any of those columns in your database, however they want (!!). Now, though, because we've set the form permissions for the 'birthdays' table in the database to 'no access', the widgets in the 'front-end' Form1 design can no longer interact directly with the database (as they did in the previous Chat app example). So, in order for the database to be accessed, we need to use code in a server module. Because server code runs on the server, it can safely interact with the database. Only the data output returned by those server functions is ever passed to the user's browser - never the code of those functions. This allows us to stop users from accessing database info and computations that need to occur securely, and only expose the results of those computations, without any potential interference from front end code.

To use data returned by server functions in front-end (client code) forms, the definitions of the server functions need to be annotated with the line '@anvil.server.callable'. Take a look at the example code above to see how this annotation appears:

@anvil.server.callable
def add_info(name, date, gift):

That function can now be called by code in a front-end client form, using the syntax:

anvil.server.call('function_name', <argument values>)

By using anvil.server.call to run server functions, any code on a front-end form can pass data argument values to any callable server functions annotated with @anvil.server.callable, and receive the data results returned by the server functions. Because the front end code can't touch any data in the database directly, and can't alter any code in the server functions, many potential security threats are stopped automatically by this arrangement. Only the returned data is exposed to the front end. Access to server functions also enables many addition sorts of computations which are difficult or impossible to perform directly in front end client code. For example, machine learning libraries which require large data sets and processing power cannot be reasonably installed in every user's browser, but they can be installed on a single server computer, and simply called with a function that runs on the server machine. Code which needs to run on a server to process credit card payments or to manage access to paid features in an account, or to connect with specialized pieces of hardware installed on a server, that perform physical activities at the geographic location of the server, for example, are all examples of the sorts of activities which need to be controlled by functions running on a server machine.

You can choose to run some functions on a server and some on client forms to distribute a web site's work load. Performing as much computational work as possible on client computers can greatly reduce the work a single server has to perform, especially on very busy web sites. One example is data validation - ensuring that data which users enter into forms, fits a proper format (i.e., checking that date entries don't include random text characters, or that phone numbers are in the form xxx-xxx-xxxx) - that's an example of computation which is often offloaded to client code. If a server is working with submissions from millions of users in a short amount of time, performing validation computations in the web browser on each client device connected to the web site can save a tremendous amount of CPU and RAM cycles on the server machine, so the web server can work faster overall for its users. The opposite can also be true - some work such as search engine spidering routines can only really be performed on a large farm of connected server machines, and client code can only be used to send search requests from the user, and to display results sent by the back-end machines. You'll learn more about which functions to run in client forms, and which ones to run on the server, just by seeing examples.

With all that explained, now edit the Form1 code in the current app, as follows:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.drop_down_1.items = {
      gifts['gift_choice'] for gifts in app_tables.gifts.search()
    }

  def button_1_click(self, **event_args):
    anvil.server.call(
      'add_info', 
      self.text_box_1.text, 
      self.date_picker_1.date, 
      self.drop_down_1.selected_value
    )
    alert (self.text_box_1.text + ' added')
    self.text_box_1.text, self.date_picker_1.date, self.drop_down_1.selected_value = '', '', 'none'

  def button_2_click(self, **event_args):
    self.repeating_panel_1.items = anvil.server.call('get_info')

  def link_2_click(self, **event_args):
    open_form('Info')

Let's go through this code piece by piece. First there are the default Anvil library imports which the IDE does for you.

Next, in the __init__ method, 2 lines of code set the items that appear in drop_down_1: a type of 'for' loop called a 'list comprehension' is used to create a Python list named 'gifts', containing items from the 'gift_choice' column, from the results of a search on the 'gifts' table in the database (we'll cover list comprehensions and loops more in a later section of this tutorial - for now just get impression of how this code syntax looks). Remember, earlier we set the front-end form permissions for the gift_choice column to 'Can search table'. This means that front-end client code on a form can read data directly from this table, but can't make any changes to the table. This makes sense because we're just reading a list of non-private data from that table (a static list of potential gift options). There's a lot to unpack in about 'for' loops and other code in that single query line! We'll complete a thorough examination of it later - for now, just look at the code to get a sense of the syntax. You'll do this sort of thing often in Python and Anvil - it will make more sense once you get used to repeatedly using code similar to this. For now, just understand that it fills the drop_down_1 widget with the list of items in the 'gift_choice' column of the 'gifts' table:

self.drop_down_1.items = {
  gifts['gift_choice'] for gifts in app_tables.gifts.search()
}

Next, the button_1_click method runs the anvil.server.call function. This operation calls the 'add_info' function on the server, and sends 3 argument values: the text typed into text_box_1, the date selected from date_picker_1, and the selected value from drop_down_1. The isolated piece of code below calls the 'add_info' server function. Get used to this syntax - you'll use something like it every time you call server functions from client code on a front-end form:

anvil.server.call(
  'add_info', 
  self.text_box_1.text, 
  self.date_picker_1.date, 
  self.drop_down_1.selected_value
)

The last 2 lines of the button_1_click method simply alerts the user with a message that their info has been submitted, and clears the data in the widgets (you've seen this sort of convenience code now several times).

Next, the button_2_click method uses anvil.server.call to execute the 'get_info' function on the server, and sets the items in repeating_panel_1 to the values returned by the server function. Get used to this code syntax - you'll use something like it whenever you need to display rows of data returned from a database query:

self.repeating_panel_1.items = anvil.server.call('get_info')

Remember, we previously set the data bindings of the 3 label widgets in the repeating panel template to the 'name', 'date', and 'gift', so each row of the repeating panel will display the appropriate fields of data returned from the database query in the get_info server function. Because each widget in the template is bound to a particular column in the results, you could rearrange the label widgets in any visual order, and they would still display their designated field results from the database, regardless of their visual position on the design layout.

Finally, the link_2_click method above just navigates to the Info form, when a user clicks the link_2 widget.

Run the app, enter some names and birthday info, click the 'show all' button, and watch the server function magic:



12.2 Adding User Accounts

Right now, users of this app can simply add to a communal list of birthdays. Perhaps that would be useful if this was just a simple personal app meant to be used by a single person, or in cases where that sort of data sharing is appropriate for group activities in an app. To make the app work privately for multiple users' data, so that each user of the app can add individual birthday info that only they will be able to access, we'll add Anvil's 'Users' service in the App Browser:



You can see that there are many configuration options for the users service, but the most common are to allow Email+Password, Google, and Facebook sign-in methods. The email option allows users to sign up for an account with an email address, and optionally to confirm that address by clicking a link in a confirmation email sent to that address (this should be a familiar routine if you've ever signed up for accounts on various web sites). The Google, Facebook, and other 1-step sign-in options offer the familiar option to just use one of those accounts to login. This saves users the time needed to set up and confirm a new account, and keeps them from having to remember a password for yet another web site. It's a good idea to offer at least the Google 1-step sign-in option.

Now, to make use of the Users service, just add the following line to the __init__ method:

anvil.users.login_with_form()

You'll see that Anvil has also imported a few new libraries needed for the Users service. Your entire Form1 code should now look like this:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.facebook.auth
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    anvil.users.login_with_form()
    self.drop_down_1.items = {gifts['gift_choice'] for gifts in app_tables.gifts.search()}

  def button_1_click(self, **event_args):
    anvil.server.call(
      'add_info', 
      self.text_box_1.text, 
      self.date_picker_1.date, 
      self.drop_down_1.selected_value
    )
    alert (self.text_box_1.text + ' added')
    self.text_box_1.text, self.date_picker_1.date, self.drop_down_1.selected_value = '', '', 'none'

  def button_2_click(self, **event_args):
    self.repeating_panel_1.items = anvil.server.call('get_info')

  def link_2_click(self, **event_args):
    open_form('Info')

Run the app, and you should now see the following sign-in form when the app starts:



To track which pieces of data are entered by a given user, simply add 3 lines of code to the add_info server function:

@anvil.server.callable
def add_info(name, date, gift):

  current_user = anvil.users.get_user()     # these 2 lines
  if current_user is not None:

    app_tables.birthdays.add_row(
      name=name, 
      date=date,
      gift=gift,

      user=current_user                     # and this one
    )

Notice the anvil.users.get.user() function and the 'if' evaluation which checks if a user is signed in before adding any data to the database. Also notice that a 4th piece of data is now added to each row in database: the current_user value (which was just generated by the anvil.users.get.user() function in the client code above), is added to the 'user' column of the database. By adding this value, each row of birthday data (each entry containing a name, date, and gift) is associated with a particular logged in user.

To make decisions about which data to return to each user, follow a similar routine in the get_info() server function, adjusting the search query so that only rows in which the 'user' column value equals the currently logged in user (user=current_user). This way, the app will only display rows of birthday data which contain the email address of the currently logged in user:

@anvil.server.callable
def get_info():

  current_user = anvil.users.get_user()
  if current_user is not None:

    return app_tables.birthdays.search(
      tables.order_by("date", ascending=True),

      user=current_user
  )

Your full server code should now look like this:

import anvil.facebook.auth
import anvil.google.auth, anvil.google.drive, anvil.google.mail
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server
# from datetime import datetime

@anvil.server.callable
def add_info(name, date, gift):
  current_user = anvil.users.get_user()
  if current_user is not None:
    app_tables.birthdays.add_row(
      name=name, 
      date=date,
      gift=gift,
      user=current_user
    )

@anvil.server.callable
def get_info():
  current_user = anvil.users.get_user()
  if current_user is not None:
    return app_tables.birthdays.search(
      tables.order_by("date", ascending=True),
      user=current_user
  )

Run the app, log in, and you'll see that only entries which you've entered while logged in are displayed when you click the 'show all' button. This will work no matter whether you log in on your home PC, a tablet, your phone, etc., because the user management functions all run on the web server computer. This will also work the same way for any other user who signs up for an account - they will only be able to see entries made with their own user account:



Look at the entries in the database, and you'll see that each row of data now contains the email address of the logged in user who created the entry. Remember, those entries were created with server functions, so they generally can't be faked.

Now this app can be used by any number of people, and they'll all only see the data in their user account (rows of data in which the user matches their currently logged in User info). That's it, a fully operational multi-user CRUD (Create Read Update Delete) database app. You'll use multi-user CRUD functionality like this in many different types of applications!

You can run this app live at http://birthdays.anvil.app

13. Image Manipulation App - Media in the Database and More Refactoring

Create a new app and choose 'Classic' as the page style (instead of Material Design), just to see how that Anvil layout looks:



Add a data table named 'my_files', set the table's form permissions to 'can search, edit and delete' (remember, this is insecure, but fine for the purposes of creating this little personal utility app), and create 2 columns named 'name' (text) and 'media_obj' (media):



Add 3 Image widgets, 1 DropDown, and 1 'FileLoader':



Edit the code for Form1 as follows:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.image

class Form1(Form1Template):
  def __init__(self, **properties):
    self.init_components(**properties)
    currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}
    self.drop_down_1.items = currentfiles

  def file_loader_1_change(self, file, **event_args):
    width, height = anvil.image.get_dimensions(file)
    alert(f"The image is {width} pixels wide and {height} pixels high")
    self.image_1.source = file
    self.image_2.source = anvil.image.rotate(file, 90)
    self.image_3.source = anvil.image.generate_thumbnail(file, 120)
    app_tables.my_files.add_row(name=file.name, media_obj=file)
    currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}
    self.drop_down_1.items = currentfiles  

  def drop_down_1_change(self, **event_args):
    if self.drop_down_1.selected_value is not None:
      filerow = app_tables.my_files.get(name=self.drop_down_1.selected_value)
      file = filerow["media_obj"]
      self.image_1.source = file
      self.image_2.source = anvil.image.rotate(file, 90)
      self.image_3.source = anvil.image.generate_thumbnail(file, 120)

Let's take a look at this code line by line, starting with the file_loader_1_change code. This method runs when the user uploads an image with the file_loader widget.

First, the variables 'width' and 'height' are set to the dimensions of the uploaded image, represented by the variable argument 'file' in the function definition. The 'get_dimensions' function in the Anvil 'image' library performs this calculation (you'll notice that the 'anvil.image' library has been imported in the 6th line of the code above):

width, height = anvil.image.get_dimensions(file)

Next, the width and height values are alerted to the user. Notice the syntax of that second line. The 'f' in the alerted string formats the text so that the variable labels in curly braces are replaced with their values in the alerted string. So this code:

f"The image is {width} pixels wide and {height} pixels high"

Evaluates to something like:

"The image is 640 pixels wide and 480 pixels high".

'Formatted string literals' or 'f-strings' like this are used regularly to output neatly displayed data in Python.

The next 3 lines set the source images of the 3 image widgets on the form design. The source file for the image_1 widget is set to the uploaded file:

self.image_1.source = file

The following line sets the source of image_2 to a version of the image which is rotated 90 degrees (using the 'rotate' function in the Anvil 'image' library - that function takes 2 arguments, the name of a file, and the number of degrees to rotate the image):

self.image_2.source = anvil.image.rotate(file, 90)

This line sets the source of image_3 to a thumbnail version of the image, resized to 120 pixels across (using the 'generate_thumbnail' function in the Anvil 'image' library)

self.image_3.source = anvil.image.generate_thumbnail(file, 120)

The next line adds the file name and the file data to a row in the 'my_files' table in the database. The 'name' field is set to the name of the file, and the 'media_obj' field is set to the file data (the image data referred to by the 'file' variable):

app_tables.my_files.add_row(name=file.name, media_obj=file)

The next 2 lines refresh the list of uploaded files, shown in the drop_down_1 widget. First, the variable label 'currentfiles' is set to the results of a search of the 'my_files' table in the database. A 'for' loop is used to collect all the items in the 'name' column of the database (you saw code similar to this in the previous birthday database example):

currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}

Finally, the 'items' property of the dropdown list is set to display the values which have just been assigned to the 'currentfiles' variable:

self.drop_down_1.items = currentfiles

That's it for the file_loader_1_change method. Run the app, and watch it go through the process of displaying the size of an uploaded image, then displaying the original, rotated, and resized versions of the image, and updating the dropdown list to show that the image has been added to the database:



Now take a look at the 'drop_down_1_change' method, line by line. This method runs when a new item is selected in the drop_down widget.

First, an 'if' conditional evaluation is used to check that the selected value in the drop_down widget is non 'None' (i.e., that something has been selected):

if self.drop_down_1.selected_value is not None:

If a value has been selected in the dropdown, then the variable 'filerow' is set to a value gotten from the database. The 'get' method is applied to the 'my_files' table, and a row is returned in which the 'name' column matches the selected value in the drop_down:

filerow = app_tables.my_files.get(name=self.drop_down_1.selected_value)

Next, the variable 'file' is set the 'media_obj' field (the image data) found in the row of data just stored in the 'filerow' variable:

file = filerow["media_obj"]

The next 3 lines are identical to the code you saw in the previous file_loader_1_change method. The image, a rotated version of the image, and resized thumbnail version of the image are displayed in the image widgets:

self.image_1.source = file
self.image_2.source = anvil.image.rotate(file, 90)
self.image_3.source = anvil.image.generate_thumbnail(file, 120)

Notice that I just mentioned that 3 lines are identical in both the drop_down_1_change method, and the file_loader_1_change method. We can 'refactor' the code a bit here to combine those 3 lines into one method, and simply call that method whenever we want to run the 3 lines. We'll call this method definition 'display_images'. The method should have 2 arguments: 'self' (so that other methods on the form can refer to it), and 'file' (so that the file data can be passed to the method):

def display_images(self, file):
    self.image_1.source = file
    self.image_2.source = anvil.image.rotate(file, 90)
    self.image_3.source = anvil.image.generate_thumbnail(file, 120)

Now, any place we need to run those 3 methods, we can just call:

self.display_images(file)

In this example, we're only refactoring 3 lines, so this example is a bit pedantic, but learning to encapsulate duplicate functionality in code is a great way to simplify and clarify code, when your programs become more complex. Our new refactored code should now look like this:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.image

class Form1(Form1Template):
  def __init__(self, **properties):
    self.init_components(**properties)
    currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}
    self.drop_down_1.items = currentfiles

  def file_loader_1_change(self, file, **event_args):
    width, height = anvil.image.get_dimensions(file)
    alert(f"The image is {width} pixels wide and {height} pixels high")
    self.display_images(file)
    app_tables.my_files.add_row(name=file.name, media_obj=file)
    currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}
    self.drop_down_1.items = currentfiles  

  def drop_down_1_change(self, **event_args):
    if self.drop_down_1.selected_value is not None:
      filerow = app_tables.my_files.get(name=self.drop_down_1.selected_value)
      file = filerow["media_obj"]
      self.display_images(file)

  def display_images(self, file):
    self.image_1.source = file
    self.image_2.source = anvil.image.rotate(file, 90)
    self.image_3.source = anvil.image.generate_thumbnail(file, 120)

The last method we need to look at is the __init__ method. Have you noticed that the only 2 lines of code in it are exactly the same as the last 2 lines of the file_loader_1_change method? Again, this is an extremely simple example to refactor, but let's take those 2 identical lines and put them in a method definition called 'refresh_dropdown':

def refresh_dropdown(self):
  currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}
  self.drop_down_1.items = currentfiles

Now any time we want to run those 2 lines, we can replace the lines with:

self.refresh_dropdown()

Here's how our newly refactored code looks:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.image

class Form1(Form1Template):
  def __init__(self, **properties):
    self.init_components(**properties)
    self.refresh_dropdown()

  def file_loader_1_change(self, file, **event_args):
    width, height = anvil.image.get_dimensions(file)
    alert(f"The image is {width} pixels wide and {height} pixels high")
    self.display_images(file)
    app_tables.my_files.add_row(name=file.name, media_obj=file)
    self.refresh_dropdown()

  def drop_down_1_change(self, **event_args):
    if self.drop_down_1.selected_value is not None:
      filerow = app_tables.my_files.get(name=self.drop_down_1.selected_value)
      file = filerow["media_obj"]
      self.display_images(file)

  def display_images(self, file):
    self.image_1.source = file
    self.image_2.source = anvil.image.rotate(file, 90)
    self.image_3.source = anvil.image.generate_thumbnail(file, 120) 

  def refresh_dropdown(self):
    currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}
    self.drop_down_1.items = currentfiles

This code is not much shorter than the original, because we only refactored 2 and 3 line routines, but as we grow the program, if we want to add more to the display or refresh functions, it will save time and potential errors, and consolidating any additional duplicate code in 1 place will shorten the program more. Even more importantly, the new code is more readable. Each separate method is now a bit shorter to read through, and the purpose of each chunk of code is a bit more clearly defined. It's easy to see that the __init__ method now simply refreshes the dropdown widget. The drop_down_1_change method loads a row of data from the database, picks the file data from that row, and then displays the images. Compare that to the original code, and it should be simpler to look through. If another person wants to edit your code, to display 4 images, for example, instead of the current 3 images, it should be a bit easier for them to see they just need to add a line to the display_images method. Any time you use chunks of code over and over in a program, it's a good idea to refactor and put duplicate lines into a clearly named method. When you come back to update a program which you haven't seen for months, you'll thank yourself for having clearly labeled chunks of code in discrete method definitions.

Run the app again, and you'll see nothing has changed about its functionality. Images are saved to the database, and users can get the resized and rotated versions at any time, by selecting from the drop down list. Users can right-click any image to download and save a copy to their hard drive:



You can run this app live at http://images.anvil.app

14. 999 Bottles of Beer - 'For' Loops, Collections and Some More Python

Before we start building the next big app, it's necessary to understand a bit more about Python lists and other 'collection' structures, as well as loops, list comprehensions and other methods of working with collections.

'Loops' are an important structure in every programming language, which allow you to 'iterate' (step progressively) through each item in a list, and do something to/with each item. Take a look at this example:

mealtimes = ['8:00am', 'noon', '6:00pm']
for mealtime in mealtimes:
  print(f'I eat at {mealtime}')

The example above prints the following output:

I eat at 8:00am
I eat at noon
I eat at 6:00pm

Let's look at each line of code. First, the variable 'mealtimes' is assigned to a list of times.

mealtimes = ['8:00am', 'noon', '6:00pm']

Next, the 'for' loop goes through each item in the 'mealtimes' list, assigning the temporary variable 'mealtime' to each successive item in the list. So, 'mealtime' first equals '8:00am', then 'mealtime' equals 'noon', then 'mealtime' equals '6:00pm'.

for mealtime in mealtimes:

The indented code on the next line takes each successive value of the 'mealtime' variable and does something with/to it:

print(f'I eat at {mealtime}')

In this case, the indented line prints a formatted string containing each successive mealtime value.

Note that you can run any plain Python example code like this by starting a new app, and putting the example code in your __init__ method definition:



In Python, you can use the 'range' function to create a list:

x = range(10)
for n in x:
  print(n)

The example above prints every number from 0 to 9. This may seem odd at first, but like many programming languages, Python is 'zero based' - counting starts at 0, and the top number in a list index is non-inclusive (counting stops 1 before the top number).

To enable more flexibility, Python allows starting and ending values to be set for a range. The following example outputs all the numbers from 1 to 10:

x = range(1, 11)
for n in x:
  print(n)

You can also set a skip value. The following code prints every number from 1 to 10, skipping by 2 (1, 3, 5, 7, 9):

x = range(1, 11, 2)
for n in x:
  print(n)

A shorter form of the example above is:

for n in range(1, 11, 2):
  print(n)

You can check if an item is in a list:

if 5 in range(1, 11):
  print(True)

Let's use a loop to print out words to the song '99 Bottles of Beer'. To do that, we'll need a range of numbers from 99 to 1, counting backwards by -1. Remember, ranges are non-inclusive of the stop number, so this range needs to go from 99 to 0:

for n in range(99, 0, -1):
  print(n)

Next, let's set some variables to contain pieces of text that can be concatenated together to form the lyrics of the song:

bob = ' bottles of beer'
otw = ' on the wall.  '
t1d = '.  Take one down, pass it around, '

Put those few pieces together, and we can generate most of the 99 Bottles of Beer lyrics, with just a few lines of code. Note that the loop variable 'n' needs to be cast to a string data type using the str() function, so that it can be joined together with the other strings (you've seen this sort of casting in earlier examples in the tutorial):

bob = ' bottles of beer'
otw = ' on the wall.  '
t1d = '.  Take one down, pass it around, '

for n in range(99, 0, -1): 
  n = str(n)
  print (n + bob + otw + n + bob + t1d + str(int(n)-1) + bob + otw)

This is great, except that last lyric should be '1 bottle of beer', instead of '1 bottles of beer'. Let's add an 'if' conditional evaluation to check if n==1, and change the 'bob' string appropriately:

bob = ' bottles of beer'
otw = ' on the wall.  '
t1d = '.  Take one down, pass it around, '

for n in range(99, 0, -1): 
  if n==1: bob = ' bottle of beer'
  n = str(n)
  print (n + bob + otw + n + bob + t1d + str(int(n)-1) + bob + otw)

Here's a screen shot of the code above running in the __init__ method of an Anvil form:



You've seen 'for' loops already a few times in the context of previous code examples in this tutorial. You'll use them regularly to do something to/with each item in the results of a database search:

for row in app_tables.sales.search():
  print row['datetime']

14.1 Slices, Strings, Dictionaries, Enumeration, Nested and Zipped Loops, List Comprehensions

You can select a 'slice' of a list using index numbers from the beginning and end of the list (remember indexes are zero-based, the first item is item zero, and the last number is non-inclusive):

letters = ['a', 'b', 'c', 'd', 'e']
print (letters[0:5])
print (letters[2:4])
print (letters[1:])
print (letters[:-2])

The code above prints:

['a', 'b', 'c', 'd', 'e']
['c', 'd']
['b', 'c', 'd', 'e']
['a', 'b', 'c']

Really take a look at the example above - you'll use list slices a lot in Python.

Slices can be used to refer to characters in text strings similarly to indexed items in lists:

text = 'Each character in this text has an index number'
print (text[0])
print (text[5:14])
print (text[-12:-7])
print (text[-6:])

The code above prints:

E
character
index
number

You can include newlines and tabs in strings using the 'escape' codes '\n' and '\t'

print (' Line 1\n Line 2 \n\tLine 3')

The line above prints:

Line 1
Line 2 
   Line 3

Python strings are 'objects', similar to other objects you've seen, such as widgets on a form design. Like those other objects, they have 'properties' (similar to the .text property of a TextBox), and they have 'methods', which are functions associated with the object (such as the .focus method of a TextBox). Below are some examples of string methods which you'll see often in Python code:

text = ' Line 1\n Line 2 \n\tLine 3'
print(
  text.strip(), '\n', '\n',
  text.lower(), '\n', '\n',
  text.upper(), '\n', '\n',
  text.startswith(' Line 1'), '\n', '\n',
  text.endswith('Line 3'), '\n', '\n',
  text.find('Line 2'), '\n', '\n',
  text.replace('Line 3', 'Last line'), '\n', '\n',
  text.join([' ', 'E', 'N', 'D']), '\n', '\n',
  len(text), '\n', '\n',
  'Line' in text, '\n', '\n',
  text.split()
)

The code above prints:

Line 1
 Line 2 
    Line 3
line 1
line 2 
  line 3 

LINE 1
LINE 2 
  LINE 3 

True 

True 

9 

Line 1
Line 2 
  Last line 

 Line 1
Line 2 
  Line 3E Line 1
Line 2 
  Line 3N Line 1
Line 2 
  Line 3D 

24 

True 

['Line', '1', 'Line', '2', 'Line', '3']

You can use Python 'dictionaries' to associate 'keys' to 'values', and use 'for' loops to step through dictionaries. Notice that dictionaries are surrounded by curly braces, where lists are instead surrounded by square brackets:

mydict = {'a' : '1', 'b' : '2', 'c' : '3'}
print (mydict['a'], mydict['c'])
for i in mydict:
  print (i[0])

The code above prints:

1 3
a
b
c

'Tuples' are immutable (unchangeable) lists. They are surrounded by parentheses:

(1, 2, 3, 4)

'Sets' are lists in which duplicate items are ignored:

x = {1, 2, 3, 4, 3, 2, 1}
print x                       # prints {1, 2, 3, 4}

You can nest 'for' loops to loop through lists of lists:

nums = [['1','2', '3'], ['a','b', 'c'], ['4', '5', '6']]
for innerlist in nums: 
  print (innerlist)
  for item in innerlist:
    print (item)

The code above prints:

['1', '2', '3']
1
2
3
['a', 'b', 'c']
a
b
c
['4', '5', '6']
4
5
6

You've already seen some examples of nested loops, which go through lists of item, within lists of rows in database results:

for row in app_tables.sales.search():
  for item in row['receipt']:
    sales_total += item['price']

Keep in mind that you can run 'if' evaluations within 'for' loops, to check for conditions and to make decisions about what to do with/to each item in a list. This is an extremely common code pattern in virtually every programming language, and you've already seen some examples of it in previous sections of the tutorial:

for row in app_tables.sales.search():
  if (
      row['datetime'] >= self.date_picker_1.date and
      row['datetime'] <= self.date_picker_2.date
    ):
    for item in row['receipt']:
     sales_total += item['price']

You can 'zip' together separate lists, and loop through the combined list values:

nums = [['1','2', '3'], ['4','5', '6'], ['7', '8', '9']]
lets = ['A', 'B', 'C']
for (nm, rw) in zip(nums, lets): u
  print (rw, nm)
  for n in nm:
    print (n)

The code above prints:

A ['1', '2', '3']
1
2
3
B ['4', '5', '6']
4
5
6
C ['7', '8', '9']
7
8
9

You can 'enumerate' lists to get an index number of each item in a loop:

lets = ['A', 'B', 'C']
for counter, item in enumerate(lets):
  print(f'{item} is at index {counter} in the list')

The code above prints:

A is at index 0 in the list
B is at index 1 in the list
C is at index 2 in the list

'List comprehensions' are a shortcut structure which simplifies the evaluation and collection of items in a list like this:

items = ['abc', 'bcd', 'cde', 'def']
items_with_c = []

for item in items:
  if 'c' in item:
    items_with_c.append(item)

print(items_with_c)

to this 1-line format:

print([item for item in items if 'c' in item])

Both examples above print:

['abc', 'bcd', 'cde']

You've seen lists, dictionaries, and list comprehensions in database queries earlier in the tutorial:

currentfiles = {my_files['name'] for my_files in app_tables.my_files.search()}

For the moment, it's OK to just learn how to use some of these basic data structures by example, and grok how they work in context. Copy and experiment with code to understand each syntax structure better. Make changes to code examples to see how printed output changes. Just recognize the syntax patterns above, get the basic gist of how they work for now, let the code sink in, within the context of working code examples. Be aware of how collection structures look, and pay attention to the presence of 'for' loops, with a basic understanding that they do something to/with every item in a list. You can use Google to find unlimited tutorials and resources about Python collections, slices, strings, and loops, until they make sense. We'll use some pieces of the code above to make our next example app.

15. Pig Latin App, Using 2 Different Loop and String Approaches

Pig Latin is defined by the Oxford dictionary as 'a secret language formed from English by transferring the initial consonant or consonant cluster of each word to the end of the word and adding a vocalic syllable (usually 'ay'): so pig Latin would be igpay atinlay.'.

To get a bit more experience using loops and strings, we'll create a simple Pig Latin generator with Anvil.

Create a new app named 'Pig Latin'. Drag a Label and 3 TextBox widgets onto the Form1 design:



Copy the following into Form1's code:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def to_pig1(self, text):
    word_list = text.split(' ')
    pig_latin = ' '
    for word in word_list:
      if word.isalpha():
        pigword = word[1:] + word[0] + 'ay'
        pig_latin = pig_latin + pigword + ' '
      else:
        pig_latin += word
    return pig_latin.strip(' ')

  def to_pig2(self, text):
    sentence = text.lower()
    words = sentence.split()
    for i, word in enumerate(words):
        if word[0] in 'aeiou':
            words[i] = words[i]+ "ay"
        else:
            has_vowel = False
            for j, letter in enumerate(word):
                if letter in 'aeiou':
                    words[i] = word[j:] + word[:j] + "ay"
                    has_vowel = True
                    break
            if(has_vowel == False):
                words[i] = words[i]+ "ay"
    pig_latin = ' '.join(words)
    return pig_latin

  def text_box_1_pressed_enter(self, **event_args):
    self.text_box_2.text = self.to_pig1(self.text_box_1.text)
    self.text_box_3.text = self.to_pig2(self.text_box_1.text)

Let's take a look at this code, line by line.

First notice that the 'text_box_1_pressed_enter' method executes the 'to_pig1' and 'to_pig2' methods, using the text which the user has entered into text_box_1 as their argument, and sets the texts of text_box_2 and text_box_3 to the results returned by those methods:

def text_box_1_pressed_enter(self, **event_args):
  self.text_box_2.text = self.to_pig1(self.text_box_1.text)
  self.text_box_3.text = self.to_pig2(self.text_box_1.text)

The 'to_pig1' method takes some text and returns the text in Pig Latin format:

def to_pig1(self, text):
    word_list = text.split(' ')
    pig_latin = ' '
    for word in word_list:
      if word.isalpha():
        pigword = word[1:] + word[0] + 'ay'
        pig_latin = pig_latin + pigword + ' '
      else:
        pig_latin += word
    return pig_latin.strip(' ')

The first line of the method above splits the 'text' argument at spaces, and assigns the results to the variable 'word_list'. Next, a string containing one space character is assigned to the variable 'pig_latin'. Next, a 'for' loop is used to go through each word in the word list and performs the following 'if' conditional evaluation upon each word:

if word.isalpha():
  pigword = word[1:] + word[0] + 'ay'
  pig_latin = pig_latin + pigword + ' '
else:
  pig_latin += word

The '.isalpha()' method of the 'word' string is used to determine if the word contains all letters. If so, the variable 'pigword' is assigned to the concatenated results of a slice of the text from index 1 to the end of the word (i.e., from the second character on), plus the first character, plus the string 'ay'. This 1 line has the effect of creating a new Pig Latin word from each input word! The Pig Latin word is then appended to the 'pig_latin' string (which will contain the full Pig Latin sentence, after the loop is complete).

If the word is not letters (i.e., if it's a punctuation mark or some other character which shouldn't have 'ay' appended), then that individual text is simply appended to the 'pig_latin' string unchanged.

The last line of the 'to_pig1' returns the constructed 'pig_latin' string, with trailing spaces removed.

The next 'to_pig2' method was taken from https://pencilprogrammer.com/python-programs/convert-sentence-to-pig-latin/ by Adarsh Kumar. It uses several of the other string methods described in the previous section of this tutorial, along with an enumerate() loop to go through each word in the method argument, without relying on the built-in '.isalpha' string method. You can read the author's description of how this method code works, on his web site. If you want to really have some fun, try Googling 'Python pig latin' and see if you can incorporate another version of some Pig Latin generator code into a third method, and have the returned results appear in a third text box in this app.

Run the current app code and play with it:



You can run this app live at http://piglatin.anvil.app

16. Mad Libs - Using Google to Find More Python Code

Wikipedia defines Mad Libs as 'a phrasal template word game created by Leonard Stern[1][2] and Roger Price.[3] It consists of one player prompting others for a list of words to substitute for blanks in a story before reading aloud.'

Let's get Google to help write a Mad Lib generator app in Python.

16.1 Version 1

The first Google result for 'python madlib' yields the following code, from the tutorial at https://www.mikedane.com/programming-languages/python/building-a-mad-libs-game/ :

color = input("Enter a color: ")
pluralNoun = input("Enter a plural noun: ")
celebrity = input("Enter a celebrity: ")

print("Roses are", color)
print(pluralNoun + " are blue")
print("I love", celebrity)

The first 3 lines of the code above assign variable labels to text requested from the user via 'input' functions. Google 'python input' to see how the input function works.

The last 3 lines print the concatenated text created by joining the user's input strings with some pre-set mad lib text.

That seems pretty simple. Pop that bit of code into the __init__ method of a new Anvil app and run it:



Let's create a UI interface design with Anvil to turn this code into an app that allows users to enter the variable strings more naturally. Drag 3 Label, 3 TextBoxs, and a TextArea onto the Form1 design, so it looks something like this:



Double-click text_box_3 and edit the text_box_3_pressed_enter code as follows:

def text_box_3_pressed_enter(self, **event_args):
  self.text_area_1.text = (
    "Roses are " + self.text_box_1.text + ".  " +
    self.text_box_2.text.capitalize() + " are blue.  " +
    "I love " + self.text_box_3.text + "!"
  )

The code above is simply a refactoring of the code we found on Google, in which the variable values originally obtained via 'input' functions are replaced by the .text properties of our Anvil text_box widgets (in the layout we created above). Notice that the text from text_box_2 has had the '.capitalize()' method applied, so that the output sentence is properly capitalized. The .text property of the text_area_1 widget is set to the concatenated results of the madlib text.

Run the code:



Great, we've got a working Mad Lib app! Let's make one more improvement to the Anvil layout. Change the name properties of each text_box widget to reflect the nature of the values which will be entered into each one (color, pluralnoun, celebrity):



and adjust the references to those text_boxes in the code:

def text_box_3_pressed_enter(self, **event_args):
  self.text_area_1.text = (
    "Roses are " + self.text_box_color.text + ".  " +
    self.text_box_pluralnoun.text.capitalize() + " are blue.  " +
    "I love " + self.text_box_celebrity.text + "!"
  )

This doesn't change anything about how the app works, but it does make the code easier to read. Adding appropriate labels to widgets is just as important as coming up with descriptive variable labels, especially in apps with a bigger code base.

You can run this version of the app at http://madlib.anvil.app

16.2 Version 2

The app above works fine, but it can only display a single Mad Lib. Let's create a new version which can assemble any number of Mad Libs. One of the Google search results for 'python mad lib' yields the following code from the tutorial at https://codeboom.wordpress.com/2012/11/22/python-madlibs/ by Laura Sach. This page contains a detailed explanation of how the code works, as well as some suggested options:

# Program to read a random madlib from a file and generate it back
import random

# Open the madlibs text file
f = open('madlibs.txt','read the lines from the file into a list
allLines = f.readlines()

# Choose a random madlib from the list
madlib = random.choice(allLines)

# Separate the input list from the story
halves = madlib.split("#")

# The second item is the madlib text 
madlibText = halves[1]

# Now get the inputs required one by one
inputs = halves[0].split(",")

inputStore = [""]

for item in inputs:
    inputStore.append(raw_input("Enter a "+item+": "))

# Print out the madlib but replace the markers with the inputs

for i in range(len(inputStore)):
    madlibText = madlibText.replace("%"+str(i), inputStore[i])

# Get rid of whitespace (this annoys me)
madlibText = madlibText.strip()
print madlibText

The code above is well documented with comments. It explains that multiple madlib strings are read from a file on the hard drive, and stored in the 'allLines' variable. A single line from the 'allLines' string is chosen from that list (using Python's 'random' library), and the result is stored in the variable 'madlib'. That line is split into 'madlibText' and 'inputs' variables, and the 'inputs' variable is further split into individual parts (which are separated by commas). A 'for' loop is used to construct an 'inputStore' variable, which contains multiple "Enter a "+item+": " lines. To clarify, each line of the 'inputStore' variable contains the concatenated text "Enter a ", plus one of the individual input items, plus a colon. Another loop is used to construct a string assigned to the 'madlibText' variable. The text of that variable is stripped of extra whitespace, and printed for the user to read.

One of the optional suggestions in the tutorial for this code is that the Madlibs can be read from an 'array', instead of being read from a file. 'Arrays' are simply a term used in other programming languages, which in Python can be interpreted to refer to 'lists':

# Set up Madlibs from array
allLines = ["person,place,time#I went to the %2 with %1 at %3",
            "object,person#A %1 flew into %2's face."
           ]

So let's refactor the example code using that suggestion, and removing the comments. Paste the code verbatim into the __init__ method of a new Anvil app:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    import random
    allLines = ["person,place,time#I went to the %2 with %1 at %3",
                "object,person#A %1 flew into %2's face."
               ]
    madlib = random.choice(allLines)
    halves = madlib.split("#")
    madlibText = halves[1]
    inputs = halves[0].split(",")
    inputStore = [""]
    for item in inputs:
      inputStore.append(raw_input("Enter a "+item+": "))
    for i in range(len(inputStore)):
      madlibText = madlibText.replace("%"+str(i), inputStore[i])
    madlibText = madlibText.strip()
    print madlibText

Run it, and we get an error 'SyntaxError: bad input at Form1, line 22'



It turns out that this code was written for an older version 2 of Python which does not require parentheses around arguments for the 'print' function. Change the last line of the code above to 'print(madlibText)' to fix that. Run the code again, and we get another error 'NameError: name 'raw_input' is not defined at Form1, line 18'. Look up 'python raw_input not defined' in Google, and you get this as the first result:

The NameError: name 'raw_input' is not defined occurs when you try to 
call the raw_input() function using Python major version 3. You can only
use raw_input() in Python 2. To solve this error, replace all instances
of raw_input() with the input() function in your program.

Ok, follow that guidance, and Tada, we get a working multiple madlib generator. The only thing is that, just like in our first version of the madlib program, it would be nice to replace those 'input' functions with something that appears nicer to the user. Google 'python anvil pop up requestor' to find a better alternative, and we find the following code at https://anvil.works/docs/client/python/alerts-and-notifications :

t = TextBox(placeholder="Email address")
alert(content=t,
      title="Enter an email address")
print(f"You entered: {t.text}")

Let's refactor the code above to fit our needs, and replace the 'input' function in the tutorial code with this:

t = TextBox(placeholder=item)
alert(content=t, title=("Enter a "+item+": "))
inputStore.append(t.text)

We can also replace the final 'print' function with 'alert'. And let's add the madlib text from the first version of our madlib app, using the format found in the 'allLines' list:

allLines = [
  "person,place,time#I went to the %2 with %1 at %3",
  "object,person#A %1 flew into %2's face.",
  "color,pluralnoun, celebrity#Roses are %1, %2 are blue.  I love %3"
]

Finally, add 'while True' to the section of code which generates the randomly chosen madlibs, and that section will repeat endlessly. Here's our final code:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    import random
    allLines = [
      "person,place,time#I went to the %2 with %1 at %3",
      "object,person#A %1 flew into %2's face.",
      "color,pluralnoun, celebrity#Roses are %1, %2 are blue.  I love %3"
    ]
    while True:
      madlib = random.choice(allLines)
      halves = madlib.split("#")
      madlibText = halves[1]
      inputs = halves[0].split(",")
      inputStore = [""]
      for item in inputs:
        t = TextBox(placeholder=item)
        alert(content=t, title=("Enter a "+item+": "))
        inputStore.append(t.text)
      for i in range(len(inputStore)):
        madlibText = madlibText.replace("%"+str(i), inputStore[i])
      madlibText = madlibText.strip()
      alert(madlibText)

Run it and play a few Mad Libs:







As you've seen, Google can be a phenomenal help in writing and de-bugging code. Whenever you don't know how to do something in code, Google it. The are so many millions of people writing Python code, getting questions answered online, and publishing tutorials, that you'll typically find exactly the answers you need right away. Polishing your Google-fu will get you much more quickly to black belt level coding mastery!

You can run this app live at http://madlib2.anvil.app

17. Email App

Google 'python anvil send email' and you'll see links to https://anvil.works/docs/email/quickstart and https://anvil.works/learn/tutorials/feedback-form/chapter-5 at the top of the search results. Notice that the first link is part of the official Anvil 'Docs' at anvil.works, and the second link is one of the many fantastic tutorials on their web site. Take a quick read through those articles, and you'll find all the code and explanations you need to build an app that sends and responds to email.

Be aware that Anvil limits the number of emails you can send per month using their servers, but you can use your own email servers to send as many emails as your own email accounts allow (you can purchase inexpensive hosted email accounts elsewhere if you need to send more mail).

Create a new Anvil app named 'Email' and add the email service:



You can optionally set up the email service to use custom server settings, if you have a third party SMTP account that you'd like to use to send emails:



We'll use some of the code from the Anvil documentation at https://anvil.works/docs/email/sending_and_receiving , to make our form operational:

anvil.email.send(
  to="customer@example.com",
  from_address="support",
  from_name="MyApp Support",
  subject="Welcome to MyApp",
  html="<h1>Welcome!</h1>"
)

Go to https://anvil.works/docs/api/ , click the 'anvil.email' link, scroll down the page to 'Message Methods and Attributes' and you'll see that 'html' and 'text' are both potential attributes of messages sent by the anvil.email library API:



This '/api/' section of Anvil's official documentation at https://anvil.works/docs/api/ is their concise reference about how all the methods and properties in the Anvil API work. You'll use it to quickly look up every possible method and attribute (value) which can be used in Anvil. Bookmark that page!

We'll change the 'html' property in the example code above to 'text', and replace the quoted strings in the example, with 'to', 'from_name', 'subject', and 'text' argument values that we'll get from the user:

anvil.email.send(
  to='',
  from_name='',
  subject='',
  text=''
)

Create a new back-end server module, and create an @anvil.server.callable function definition named 'sendmail', which contains the anvil.email.send function above. The function should take (to, from_name, subject, text) arguments, and use those arguments as the values passed to the anvil.email.send function:

@anvil.server.callable
def sendmail(to, from_name, subject, text):
  anvil.email.send(
    to=to,
    from_name=from_name,
    subject=subject,
    text=text
  )

You'll notice that Anvil automatically imports the anvil.email library:

import anvil.email
import anvil.server

Now add 4 Labels, 3 TextBoxs, 1 TextArea, and 1 Button to the front-end Form1 design:



To make the function above work in our app, double-click the 'Submit' button, and add the code below. When the button is clicked, the values entered into the text_box and text_area widgets are sent as arguments to the 'sendmail' server function. An alert is also used to let the user know the email has been sent:

def button_1_click(self, **event_args):
  anvil.server.call(
    'sendmail',
    self.text_box_1.text,
    self.text_box_2.text,
    self.text_box_3.text,
    self.text_area_1.text
  )
  alert('Sent!')

Run the app, and you'll see you've got a working email client app that you can use to send emails.



You can edit your sendmail function to add attachments, and see more details at the Anvil email documentation links above:

anvil.email.send(
  from_name='Support team', 
  to='someone@site.com', 
  subject='File attached',
  text='Please see attached text file',
  attachments=[
    anvil.BlobMedia('text/plain', 'Important message!', name='msg.txt')
  ]
)

Now publish your app and copy the URL link to your app:





Add the following additional function to your server code, to reply to incoming emails:

@anvil.email.handle_message
def message_handler(msg):
  print('Email received from %s' % msg.addressees.from_address.raw_value)
  msg.reply(text="Your message to emails.anvil.app has been received")

A working version of this app can be found at http://emails.anvil.app, so the function in that app will receive/reply to any emails sent to anything@emails.anvil.app.

Now add the data table service to your app, create a database table named 'emails', and add 4 text columns named 'from', 'to', 'text', and 'html':



Add another table named 'attachments', with a text column named 'message' and media column named 'attachment':



To save incoming mails and attachments to the database, change your @anvil.email.handle_message function as follows:

@anvil.email.handle_message
def handle_incoming_emails(msg):
  msg.reply(text='Your message has been received.')
  msg_row = app_tables.emails.add_row(
              from_addr=msg.envelope.from_address, 
              to=msg.envelope.recipient,
              text=msg.text, 
              html=msg.html
            )
  for a in msg.attachments:
    app_tables.attachments.add_row(
      message=msg_row, 
      attachment=a
    )



That's it, you've got a fully functioning email app which allows you to send emails to anyone, autorespond to any incoming messages, and save emails and attachments to a database. Hopefully you can keep in mind what you've seen about the Anvil docs, including the API reference. You'll use those resources regularly when building apps with Anvil.

18. Calculator App - Creating UI Design Layouts with Pure Anvil Code

The user interface layout of every app we've created so far has been designed using Anvil's drag-and-drop UI builder (by dragging widgets from the toolbox onto a form design). In some applications, it can be helpful to use code to build the user interface.

Create a new app named 'Calculator', and copy the following code to the __init__ method of Form1:

self.txt = TextBox()
self.add_component(self.txt)

Run the app, and you'll see that a TextBox widget has been added to the design layout, without dragging a widget from the Toolbox onto the Form1 visual design:



Notice that autocomplete really helps walk you through the process of adding widgets to the layout. It not only shows you which widgets can be added, but also shows you all the 'Properties' settings that you're used to adjusting by pointing and clicking in the right hand column of the IDE:



Next, add a GridPanel to the layout, in the same way the TextBox was added above:

grd = GridPanel()
self.add_component(grd)

The GridPanel is an invisible layout container - lets add a Button widget to it:

grd = GridPanel()
btn = Button(text='1')
grd.add_component(btn, row=1, col_xs=4, width_xs=1)
self.add_component(grd)

The code above does the same thing as dragging a GridPanel onto the Form1 design, and then dragging a Button widget into row 1 of the Grid panel:



Now let's add a 'click' event handler to the button, which runs the 'self.click' method whenever the button is clicked by the user. We'll also create the 'click' method definition, which just prints the text on the button widget:

grd = GridPanel()
btn = Button(text='1')
btn.set_event_handler('click', self.click)
grd.add_component(btn, row=1, col_xs=4, width_xs=1)
self.add_component(grd)

def click(self, **event_args):
  print (event_args['sender'].text)

Here's the Form1 code so far:

from ._anvil_designer import Form1Template
from anvil import *
class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.txt = TextBox()
    self.add_component(self.txt)
    grd = GridPanel()
    btn = Button(text='1')
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=1, col_xs=4, width_xs=1)
    self.add_component(grd)

  def click(self, **event_args):
    print (event_args['sender'].text)

So far, this works just as if we'd dragged some widgets onto the design, set some properties, and added some code to the widget_click method - as you've seen in so many of the previous examples.

Now let's have the button's 'click' method update the text displayed in the TextBox widget:

def click(self, **event_args):
  chr = event_args['sender'].text
  self.txt.text += chr

Note that += is a Python operator for adding items to an existing list or string. Writing this:

self.txt.text = self.txt.text + chr

is the same as writing:

self.txt.text += chr

Boths lines of code above simply add the character represent by the variable 'chr', to the text in the 'txt' widget.

Run the app, and watch the character '1' get added to the TextBox every time you click the button:



Great, so now we could just duplicate the code for each button we need for our calculator display, adjusting the text on each widget, and the widget's 'row' property, to position each one in the Gridlayout. Here's a little sample code which does that, up to number 4:

from ._anvil_designer import Form1Template
from anvil import *
class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.txt = TextBox()
    self.add_component(self.txt)
    grd = GridPanel()

    btn = Button(text='1')
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=1, col_xs=4, width_xs=1)

    btn = Button(text='2')
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=1, col_xs=4, width_xs=1)

    btn = Button(text='3')
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=1, col_xs=4, width_xs=1)

    btn = Button(text='+')
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=1, col_xs=4, width_xs=1)

    btn = Button(text='4')
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=2, col_xs=4, width_xs=1)

    self.add_component(grd)

  def click(self, **event_args):
    chr = event_args['sender'].text
    self.txt.text += chr

Run the code above, and you'll see that it works as expected:



But that requires a ton of duplicate code! Instead of adding each Button's layout code manually to the design, we can instead use a 'for' loop. First make a list of all the buttons we want to add:

nums = [['1','2', '3','+'], ['4','5', '6', '-'], 
        ['7', '8', '9', '.'], ['0', '*', '/', '=']]

Make another list of the rows in which each group of buttons above should appear in the GridPanel:

rows = ['A', 'B', 'C', 'D']

Now use 2 nested 'for' loops and the 'zip' function demonstrated earlier, to add each of buttons and it's click event handler to the GridPanel layout (which we labeled 'grd' earlier):

for (nm, rw) in zip(nums, rows): 
  for n in nm:
    btn = Button(text=n)
    btn.set_event_handler('click', self.click)
    grd.add_component(btn, row=rw, col_xs=4, width_xs=1)

self.add_component(grd)

Here's the whole program so far:

from ._anvil_designer import Form1Template
from anvil import *
class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.txt = TextBox()
    self.add_component(self.txt)
    grd = GridPanel()

    nums = [['1','2', '3','+'], ['4','5', '6', '-'], 
            ['7', '8', '9', '.'], ['0', '*', '/', '=']]
    rows = ['A', 'B', 'C', 'D']
    for (nm, rw) in zip(nums, rows): 
      for n in nm:
        btn = Button(text=n)
        btn.set_event_handler('click', self.click)
        grd.add_component(btn, row=rw, col_xs=4, width_xs=1)

    self.add_component(grd)

  def click(self, **event_args):
    chr = event_args['sender'].text
    self.txt.text += chr

That's 'much' better than having to add the code for every button manually! Run the code and see how the entire layout has been created, and each button appends its own face text to the TextBox display:



There's just one issue. When the '=' button is clicked, the program simply adds the equal sign to the text display, like every other button, and the computation isn't evaluated. Fix that by adding the following 'if' evaluation to the 'click' method. If the character on the button is '=' then the 'eval' function is used to evaluate the expression in the Textbox, and then text in the TextBox is changed to that evaluated value. In other words, the calculation displayed in the Textbox is performed, and the result of that calculation is then displayed in the Textbox (as you'd expect of a calculator):

if chr == '=': 
  self.txt.text = eval(self.txt.text[:-1])

Notice in the line above that the 'eval' function is actually performed on all but the last character in the TextBox (the [:-1] slice means take everything in the string except the last character). The reason this is needed is because this code is run when the '=' button is pressed. We don't want that appended equal symbol in the TextBox to be included in the evaluated expression.

At this point, we have a fully working calculator app, which has been created entirely in code (absolutely no part of this application was created with the drag-and-drop toolkit):

from ._anvil_designer import Form1Template
from anvil import *
class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.txt = TextBox()
    self.add_component(self.txt)
    grd = GridPanel()
    nums = [['1','2', '3','+'], ['4','5', '6', '-'], 
            ['7', '8', '9', '.'], ['0', '*', '/', '=']]
    rows = ['A', 'B', 'C', 'D']
    for (nm, rw) in zip(nums, rows): 
      for n in nm:
        btn = Button(text=n)
        btn.set_event_handler('click', self.click)
        grd.add_component(btn, row=rw, col_xs=4, width_xs=1)
    self.add_component(grd)

  def click(self, **event_args):
    chr = event_args['sender'].text
    self.txt.text += chr
    if chr == '=': 
      self.txt.text = eval(self.txt.text[:-1])

Take a break from digesting the new concepts and code patterns in this example, and play with the app at http://calculate.anvil.app

19. Weather App - Using HTTP REST APIs in Anvil

One of the most commonly required tasks in modern software development is to make use of 'APIs' provided by third party web sites. The term 'API' technically stands for 'Application Programming Interface', and the use of the term sometimes gets confusing. The 'Anvil API' consists of the full set of functions and features in the Anvil framework, implemented in the IDE, etc. You may also hear people talk about 'the Java API', for example, which refers to all the functions in the standard library that comes default with the Java language. You may also hear about APIs for using particular pieces of hardware (such as Raspberry Pi microcontrollers), or about the API of game engines such as Unity, for example. When talking about 'web' APIs, however, the term typically refers to a service offering by some web site which is accessible by 'HTTP' requests, usually conforming to 'REST' practices.

HTTP is the transfer protocol used to transmit web pages on the Internet. If you look in your browser's address bar whenever you go to a web site, you'll see that the address of every site begins with 'http://' or 'https://' (https is the secure version of HTTP). Documents at a web site are typically stored in folders on a server computer, and served by a 'web server' program such as Apache. The address of a static document (an unchanging web page) typically refers to its folder location on the hard drive of the server computer. For example, 'www.mysite.com/index.html' refers to the index.html file in the root 'public_html' folder on the web server hard drive. 'www.mysite.com/myapp/myscript.cgi' refers to a 'CGI' program found in the 'myapp' subfolder on the hard drive.

You can send data to a web app URL by following the URL of a script with a question mark and variable/value pairs, separated by ampersands. For example, 'www.mysite.com/myapp/myscript.cgi?name=john&age=42' sends the myscript.cgi program something equivalent to the Python variable names and values 'name=john' and 'age=42'. The script at that URL can then process the transferred values and return a result to the user's browser. Depending upon how the script is designed, the result sent back to the user's browser could be a full web page to be viewed by a user, or it could be just a few simple data values. The concept of interacting with a web server at a given URL, sending a request for information using a URL string encoded in HTTP format, and then receiving some computed data in return, is the basis of how web APIs work.

You can find web APIs which allow you to make video calls in your browser, to send text messages, to purchase stocks, and much more. In this app example, we'll use a weather api available at openweathermap.org, to display the current temperature and wind speed at any zip code, in Anvil. To use almost any online API, you'll need to create an account with the web site that provides the API service, and get an 'API Key'. You can create a free account at https://openweathermap.org/. You'll find your API key in your account pages:



Copy your key and save it. Be aware of the usage limitations for your account. Most web APIs provide various tiers of use for different prices, depending upon how much traffic and computing resources are consumed.

To use the openweathermap API in Anvil, click the 'Api' link on their homepage, or go directly to https://openweathermap.org/api :



Scroll down and click 'Built-in API request by zip code':



Copy the example API call:

api.openweathermap.org/data/2.5/forecast?zip={zip code},{country code}&appid={API key}

Notice the 'Parameters' documentation below the example. This explains exactly how to use the API. For our purposes, all we need to do is replace {zip code} with the actual zip code of an address, and {API key} with the API key that has been provided in your account. Try pasting the following into your browser:

https://api.openweathermap.org/data/2.5/forecast?zip=07006&appid=9db8c68f79faed11f8bcadc9dadedef2

You should see the service send back a page of data in 'Json' format:



To use this data in Anvil, create a new app and add the following code to the __init__ method:

response = anvil.http.request(
'https://api.openweathermap.org/data/2.5/weather?zip=07006&appid=9db8c68f79faed11f8bcadc9dadedef2'
)
resp = response.get_bytes()
print (f'{resp}')

In the code above, the anvil.http.request() function sends a request to the openweathermap URL. The data in that response is assigned to the variable 'response'. The '.get_bytes' method on the next line converts that data into a Python printable format. Run the app, and you should see the raw data retrieved from the API, printed to the Anvil Console.

Because data from this API is returned in Json format ('JavaScript Object Notation'), Anvil can make better use of it by including 'json=True' in the 'request' function call (be sure to capitalize 'True'). You'll notice 'temp' and 'speed' values within 'main' and 'wind' sections of the raw Json data returned by the API. Adjust your __init__ code as follows to use those values in Anvil:

response = anvil.http.request(
'https://api.openweathermap.org/data/2.5/weather?zip=07006&appid=9db8c68f79faed11f8bcadc9dadedef2',
  json=True
)
wind = response['wind']['speed']
temp = response['main']['temp']
print (f'Wind speed is {wind}, and temperature is {temp}')

Run the app, and the following will be printed in the Anvil console (with whatever the current values are):

Wind speed is 3.6, and temperature is 298.37

We can break the API key and zip code values back out into variables, and use an f-string to compose the request URL. We can also add '&units=imperial' to the request string, to change the data from metric to imperial units that are familiar in the US:

id = "9db8c68f79faed11f8bcadc9dadedef2"
zipc = "07006"
response = anvil.http.request(
  f'https://api.openweathermap.org/data/2.5/weather?zip={zipc}&appid={id}&units=imperial',
  json=True
)
wind = response['wind']['speed']
temp = response['main']['temp']
print (f'Wind speed is {wind}, and temperature is {temp}')

Now we can design a little user interface to get the zip code entered by a user, and to display wind and temp results nicely on a page. Drag 3 Labels, a TextBox, and 2 TextAreas onto the Form1 design:



Edit your Form1 code as follows. Notice that an import of the anvil.http library has been added automatically by the Anvil IDE:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.http

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def text_box_1_pressed_enter(self, **event_args):
    id = '9db8c68f79faed11f8bcadc9dadedef2'
    zipc=self.text_box_1.text
    json = anvil.http.request(
      f'https://api.openweathermap.org/data/2.5/weather?zip={zipc}&appid={id}&units=imperial',
      json=True
    )
    wind = json['wind']['speed']
    self.text_area_2.text = wind
    temp = json['main']['temp']
    self.text_area_3.text = temp

Hopefully, this code makes sense at this point. The 'text_box_1_pressed_enter' method is executed when the user enters a value into the text box. In this method definition, the anvil.http.request() function gets data from openweathermap.org, then the 'wind' and 'temp' values are displayed in the TextAreas on the form. Run the app and try entering a few zip codes:



There's a security issue with this code that should be addressed. Right now, my private API key is displayed in the code for all to see:

id='9db8c68f79faed11f8bcadc9dadedef2'

In order to keep that private information out of the code, we can add the Anvil 'App Secrets' service. Enter an 'ID' for the value, and click 'Set Value' to paste in the private API key:





Values stored in App Secrets can only be retrieved from back-end server code, so add a server module and create a callable function to retrieve the secret ID value:

import anvil.secrets
import anvil.server

@anvil.server.callable
def get_id():
  return anvil.secrets.get_secret("id")



You can get the value returned by the function above, by using anvil.server.call() in your front end code (you've seen examples of this routine in previous sections of the tutorial):

id = anvil.server.call('get_id')

Now just replace the line below, with the line above:

id='9db8c68f79faed11f8bcadc9dadedef2'

And now your secret API key is hidden from the code of your app:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.server
import anvil.http

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def text_box_1_pressed_enter(self, **event_args):
    id = anvil.server.call('get_id')
    zipc=self.text_box_1.text
    json = anvil.http.request(
      f'https://api.openweathermap.org/data/2.5/weather?zip={zipc}&appid={id}&units=imperial',
      json=True
    )
    wind = json['wind']['speed']
    self.text_area_2.text = wind
    temp = json['main']['temp']
    self.text_area_3.text = temp

You can see this app running live at http://weatherapi.anvil.app. There are many tutorials and example apps on the anvil.works web site which demonstrate how to use all sorts of exciting web APIs to achieve machine learning, artificial intelligence, communications, and other useful computing goals. Now that you know the basics of how to send requests and receive response data, you'll see that really powerful computing capabilities can be achieved easily with any of thousands of APIs that abound in the wild. Take a look at the Anvil Tutorials and API docs to see more about how to format complex data requests and how to use results in interesting ways!

20. Database and IP Address Service App Examples - Creating HTTP REST APIs with Anvil

In the previous section, you got to see a bit about how to interact with existing web APIs. Now, this section will demonstrate how to create web APIs with Anvil, which can be used by others.

One of the great things about web APIs is that most popular programming languages support ways to interact with them. This means that web APIs are mostly 'language agnostic'. For example, at the moment you don't know or really care what programming language was used to create the openweathermap.org API. It could be written in Java, JavaScript, Python, Ruby, C#, etc., and hosted with any kind of web server software, on any a sort of computer, running on any operating system, etc. The standardized interface of using HTTP to send requests, and Json data structures to receive information results, is all that's needed to make a web API universally usable ('XML' and other common data formats are also used in web APIs). Anvil provides tools that make it simple to create APIs which others can use to interact with your app's functionality via HTTP API 'endpoints'.

Create a new app named 'API Services'. Add a database table named 'somedata', with 1 column named 'items', and fill that column with some rows of dummy data ('item1', 'item2', 'item3', 'item4', 'item5'). Set the table's form permissions to 'no access' (you've done all these things in previous tutorial examples):



Now add a server module, with this code:

@anvil.server.http_endpoint("/somedata/list")
def get_items_list():
  return [u['items'] for u in app_tables.somedata.search()]

Notice that the function above is decorated with @anvil.server.http_endpoint("/somedata/list"). If you publish your app, and append '/_/api/somedata/list' to the URL of the app, you'll see the database search results returned by the 1 line of code in the function definition above (you've seen database queries similar to this in previous examples):



To be clear this, demo app has been published at:

https://apiservice.anvil.app/

So access to results from the function above can be accessed at:

https://apiservice.anvil.app/_/api/somedata/list

There is no front end design to this app. We're just going to add more API functions which can be accessed via additional HTTP API endpoint URLs.

For a moment just imagine that you could, for example, build up the function above to retrieve any other sort of information you have stored in any other database tables, or which has been computed in some way that is valuable, and that you can use the data output from that function in any other Anvil app (using the anvil.http.request() function you learned about in the previous section). More importantly, you can provide that computation and data output service as a web API which can be accessed and used in virtually any other programming language, running on any sort of computer, anywhere on the Internet. This simple API capability can be extended to perform virtually any sort of computing task imaginable. Because Anvil includes the full power of the entire Python ecosystem, you can import libraries to perform complex data processing computations of virtually any sort, and provide results to users via the web API interface.

Let's add a few more function examples:

You can create a function which accepts data sent by the user:

@anvil.server.http_endpoint("/echoback/:id")
def echo_back(id, **params):
  return f"You requested id {id} with params {params}"

The user can send data values to that HTTP endpoint, using the question mark syntax in the HTTP URL (this is called HTTP 'GET' method):

https://apiservice.anvil.app/_/api/echoback/2?x=items%201

If you paste the above URL in a browser address bar, you'll see the following response returned by the function:

You requested id 2 with params {'x': 'items 1'}

The ability to pass data values to a web API is very powerful, because the API function can perform any sort of imaginable computation upon the submitted values. Add this function to your server code, to see how the submitted data can be compared to items returned by a search of database tables:

@anvil.server.http_endpoint("/searchdb/:id")
def search_db(id, **params):
  items = [u['items'] for u in app_tables.somedata.search()]
  index = items.index(params['x'])
  return f'''"{params['x']}" is item #{index + 1} in the database'''

Visit the following URL in your browser:

https://apiservice.anvil.app/_/api/searchdb/1?x=item4

and you'll see the following response from the function above:

"item4" is item #4 in the database

Try changing 'x=item4' in the URL above to 'x=item2', and then to 'x=item3', and you'll see the returned results match the live contents of the actual database search each time. That's powerful stuff! With just a few lines of code, you can provide users with full access to results from any database system you create... and don't forget, that database can be part of any application you've created in Anvil. Just add the '@anvil.server.http_endpoint' decorator and a unique endpoint (like ("/searchdb/:id") in the function above) to any server function you create, and you've got a web API that can be used to interact with the data and computations in your app. It's that simple.

You can get information such as the IP address of the user who submitted the request:

@anvil.server.http_endpoint("/ip/:id")
def get_ip(id):
  ip = anvil.server.request.remote_address
  return f"You requested user {id} from IP {ip}"

Run the API above at:

https://apiservice.anvil.app/_/api/ip/2

And you should receive a response like the following (but with your IP address):

You requested user 2 from IP 72.250.62.78

You can also send back HTTP response codes and set headers in the returned response:

@anvil.server.http_endpoint("/redirect")
def serve_content():
  response = anvil.server.HttpResponse(302, "Redirecting...")
  response.headers['Location'] = "http://com-pute.com"
  return response

Run the function above by visiting the following URL:

https://apiservice.anvil.app/_/api/redirect

and your browser will be redirected to open up this URL (the com-pute.com web page will open in your browser):

http://com-pute.com/

There are many more web API examples on the anvil.works web site. Now that you know the basics, you can learn to add some additional features that Anvil makes available via API endpoints, but most of the work required to create useful APIs, is in creating server functions that provide some useful information or valuable computation to users. You could, for example, set up an API function definition which uses Python libraries to create 3D graph visualizations of submitted values, or use a trained machine learning library to respond with artificial intelligence to human chat submissions, or send control codes that move robotic equipment... The possibilities of what you can do with web APIs are limited only to the full power of what's capable in the entirety of the Python ecosystem (that's virtually endless). Web APIs simply provide a standardized interface that enable other languages and computing tools to connect with functions you write. Can you imagine any sort of online service you could build, with this capability?

21. Hangman Game App - Drawing Canvas Graphics in Anvil

This section of the tutorial will demonstrate how to use Anvil's 'canvas' API to draw images using graphic fundamentals such as lines, squares and circles. We'll make a simple version of the classic Hangman game, in which users try to guess the letters of a hidden word, before a full body graphic gets drawn.

Create a new app named 'Hangman', click 'See More Components' in the Toolbox, and drag a 'Canvas' component on the Form1 design. Resize the canvas to increase its height to 300 pixels (you can also do this by setting the canvas's height property to 300):



Now click the main Form1 layout (somewhere on the form where there aren't any widgets), and edit the 'form_show' event by clicking the '>>' button next to the 'show' event handler:



Drop in the following example code from the Anvil Docs at https://anvil.works/docs/client/components/canvas :

def form_show(self, **event_args):
  """This method is called when the Canvas is shown on the screen"""
  c = self.canvas_1
c.begin_path()
c.move_to(100,100)
c.line_to(100,200)
c.line_to(200,200)
c.close_path()
c.stroke_style = "#2196F3"
c.line_width = 3
c.fill_style = "#E0E0E0"
c.fill()
c.stroke()

Run the app and you'll see this graphic:



If you don't see an image, be sure to set the 'show' event to run the 'show_Form1' method definition, as described above - this is easy to miss.

To draw just a line, the bits of code we need from the example above are:

c = self.canvas_1
c.move_to(100,100)
c.line_to(100,200)
c.stroke()

Replace the form_show method code with the example above, and run the app. You'll see just one vertical line on the screen.

Now let's draw a circle. Replace the form_show method definition above with the following code:

c = self.canvas_1
c.fill_style = 'black'
c.begin_path()
c.arc(100, 100, 20, 0, 360)
c.fill()
c.close_path()
c.stroke()

Run the app and you get a circle on the screen:



We can draw a stick figure of a person with 1 circle and 5 lines:

def form_show(self, **event_args):
  c = self.canvas_1
  c.fill_style = 'black'
  c.begin_path()
  c.arc(100, 100, 20, 0, 360)
  c.fill()
  c.close_path()
  c.stroke()
  c.move_to(100,100)
  c.line_to(100,200)
  c.stroke()
  c.move_to(100,150)
  c.line_to(20, 120)
  c.stroke()
  c.move_to(100,150)
  c.line_to(180,120)
  c.stroke()
  c.move_to(100,200)
  c.line_to(140,280)
  c.stroke()
  c.move_to(100,200)
  c.line_to(60,280)
  c.stroke()



To create the hangman app, the basic logic will be to:

1) Randomly select a word from a list.
2) Display the word in a Label widget, 
   but with unknown characters replaced by asterisks.
3) Allow the user to select a letter.
   Append the selected letter to a list in a text_box.
   If the letter is found in the word, replace the
     asterisks with that letter, anywhere the letter
     is found in the word.
   If the letter is not found in the word, draw 1
     body part (circle or line) of the stick figure
     graphic above.
4) If the stick figure graphic gets fully drawn, alert
   the user that they've lost.
5) If the user has guessed all the letters in the word,
   alert that they've won.

To create the game's UI, drag 4 labels, 2 text_boxes, 1 drop_down, 1 spacer, and 1 canvas onto the design of Form1:



Set the 'items' property of the drop_down widget to all the letters of the alphabet, 1 per line:

a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z



Set the font size of the label_1 widget to 30:



Now set this as the code for Form1:

from ._anvil_designer import Form1Template
from anvil import *
import random

word = random.choice([
  "black", "white", "gray", "silver", 
  "maroon", "red", "purple", "fushsia", 
  "green", "lime", "olive", "yellow", 
  "navy", "blue", "teal", "aqua"
])

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.label_1.text = word
    self.asterisks()

  def asterisks(self):
    starred_word = ''
    for char in word:
      if -1 == self.text_box_1.text.find(char):
        starred_word += '*'
      else:
        starred_word += char
    self.label_1.text = starred_word
    self.form_show()

  def form_show(self, **event_args):
    c = self.canvas_1
    c.fill_style = 'black'
    if len(self.text_box_2.text) >= 1:
      c.begin_path()
      c.arc(100, 100, 20, 0, 360)
      c.fill()
      c.close_path()
      c.stroke()
    if len(self.text_box_2.text) >= 2:
      c.move_to(100,100)
      c.line_to(100,200)
      c.stroke()
    if len(self.text_box_2.text) >= 3:
      c.move_to(100,150)
      c.line_to(20, 120)
      c.stroke()
    if len(self.text_box_2.text) >= 4:
      c.move_to(100,150)
      c.line_to(180,120)
      c.stroke()
    if len(self.text_box_2.text) >= 5:
      c.move_to(100,200)
      c.line_to(140,280)
      c.stroke()
    if len(self.text_box_2.text) >= 6:
      c.move_to(100,200)
      c.line_to(60,280)
      c.stroke()
      alert('You lose!')

  def drop_down_1_change(self, **event_args):
    self.text_box_1.text += self.drop_down_1.selected_value
    if -1 == word.find(self.drop_down_1.selected_value):
      self.text_box_2.text += self.drop_down_1.selected_value
    self.asterisks()
    if word == self.label_1.text:
      alert('You win!')

Let's go through all the code above, line by line.

First, notice that the standard Python 'random' library is imported. The random.choice function is used to pick a random string from a list of colors. This string is assigned the variable label 'word'. The user will try to guess this word while playing the game.

Next, the __init__ method sets the text property of the label_1 widget to display the 'word' string. It also executes the 'asterisks' method, which replaces unknown characters of the 'word' string, with asterisks:

starred_word = ''
for char in word:
  if -1 == self.text_box_1.text.find(char):
    starred_word += '*'
  else:
    starred_word += char
self.label_1.text = starred_word
self.form_show()

The first line above sets the variable 'starred_word' to an empty string. Next, a 'for' loop goes through each character in the 'word' string, and if the character is not found in text_box_1, then an asterisk is added to the 'starred_word' string. If the character is found in text_box_1, then the character itself is added to the 'starred_word' string. Since there is no text in text_box_1 when the program starts, the entire 'word' string is replaced with asterisks, and the user is left to guess characters. Notice that the last line of the method above calls the 'form_show()' method, but there's nothing to draw on the form yet, so that method doesn't do anything at this point.

When the user selects a letter form the drop_down_1 widget, the drop_down_1_change method is executed:

self.text_box_1.text += self.drop_down_1.selected_value
if -1 == word.find(self.drop_down_1.selected_value):
  self.text_box_2.text += self.drop_down_1.selected_value
self.asterisks()
if word == self.label_1.text:
  alert('You win!')

The first line of the code above adds the selected character to text_box_1. The second line uses an if conditional evaluation to test whether or not the selected letter is found in the 'word' string. If not (if the word.find method yields -1), then the selected value from drop_down_1 is appended to the text in text_box_2. Next, the 'asterisks' method is executed again, where any correctly guessed letters are revealed in the label_1 widget (and unknown characters are again replaced with asterisks). Finally, if the text displayed in the label_1 widget matches the text value of the 'word' variable, then the user is alerted with a message that they win.

Keep in mind that every time the 'asterisks' method is executed (as it is when the drop_down_1_change method above is executed), it in turn executes the 'form_show' method:

def form_show(self, **event_args):
  c = self.canvas_1
  c.fill_style = 'black'
  if len(self.text_box_2.text) >= 1:
    c.begin_path()
    c.arc(100, 100, 20, 0, 360)
    c.fill()
    c.close_path()
    c.stroke()
  if len(self.text_box_2.text) >= 2:
    c.move_to(100,100)
    c.line_to(100,200)
    c.stroke()
  if len(self.text_box_2.text) >= 3:
    c.move_to(100,150)
    c.line_to(20, 120)
    c.stroke()
  if len(self.text_box_2.text) >= 4:
    c.move_to(100,150)
    c.line_to(180,120)
    c.stroke()
  if len(self.text_box_2.text) >= 5:
    c.move_to(100,200)
    c.line_to(140,280)
    c.stroke()
  if len(self.text_box_2.text) >= 6:
    c.move_to(100,200)
    c.line_to(60,280)
    c.stroke()
    alert('You lose!')

This method definition simply draws the stick figure graphic you saw earlier, whenever a new character is added to text_box_2 (take a look back at the drop_down_1_change method - it adds each incorrectly guessed character to text_box_2). This method just uses a series of 'if' evaluations to draw consecutive pieces of the stick figure graphic, based on how many characters are displayed in text_box_2 (the len() function is used to determine the length of strings). If the entire graphic has been drawn (i.e., the length of the text in text_box_2 is 6 or more), then the user is alerted with a message that they've lost the game.

Run the app and play a few games:



You can see it live at http://hangman.anvil.app

22. Guitar Chords App - More Graphics Drawing, for a Higher Purpose!

This next app will display images of guitar chord diagrams, drawn on screen using music theory formulas which define how 'intervals' make up each chord layout.

Create a new app named 'Guitar Chords', click 'See More Components' in the Toolbox, and drag a 'Canvas' component onto the Form1 design. Resize the canvas to increase its height to 350 pixels:



Now add the following code to the 'show_Form1' method definition, which handles the 'show' event of Form1 (just like in the hangman app):

c = self.canvas_1
c.stroke_style = "black"
c.clear_rect(0, 0, 400, 300)
for i in range(20, 111, 20):
  for j in range(20, 141, 20):
    c.stroke_rect(i, j, 20, 20)
for i in range(220, 311, 20):
  for j in range(20, 141, 20):
    c.stroke_rect(i, j, 20, 20)

The code above uses 2 'for' loops to draw a series of 20x20 pixel, clear rectangular boxes which form 2 graphic grids on the screen. This represents strings and frets within 2 standard guitar diagram images.

Next, we'll define a dictionary of 'interval' names, which map to the X-Y pixel locations of each interval, at which circles will be drawn on the grid, to form chords:

circles_root5 = {
  '1' : [40, 70],
  '11' : [80, 110],
  '111' : [100, 30],
  '3' : [100, 110],
  '33' : [60, 50],
  'b33' : [60, 30],
  '5' : [120, 70],
  '55' : [60, 110],
  'b5' : [120, 50],
  '7' : [80, 90], 
  'b7' : [80, 70],
  '9' : [100, 70],
  '6' : [80, 50],
  '13' : [120, 110],
  '4' : [100, 130],
  '44' : [60, 70],
  '444' : [120, 30],
  '99' : [80, 150],
  'b3' : [100, 90],
  'b9' : [100, 50],
  'b6' : [120, 90],
  'b66' : [60, 130],
  'b55' : [60, 90]
}

Now add the following code to the 'show_Form1' method definition:

for key, value in circles_root5.items():
  if key is '1':
    c.fill_style = 'white'
  else:
    c.fill_style = 'black'
  c.begin_path()
  c.arc(value[0],value[1], 5, 0, 360)
  c.fill()
  c.close_path()
  c.stroke()

The code above uses a 'for' loop to draw a circle at each location in the 'circles_root5' list, over top of the already drawn rectangular grids.

So far, the code for Form1 should look like this:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def form_show(self, **event_args):
    c = self.canvas_1
    c.stroke_style = "black"
    c.clear_rect(0, 0, 400, 300)
    for i in range(20, 111, 20):
      for j in range(20, 141, 20):
        c.stroke_rect(i, j, 20, 20)
    for i in range(220, 311, 20):
      for j in range(20, 141, 20):
        c.stroke_rect(i, j, 20, 20)

    circles_root5 = {
      '1' : [40, 70],
      '11' : [80, 110],
      '111' : [100, 30],
      '3' : [100, 110],
      '33' : [60, 50],
      'b33' : [60, 30],
      '5' : [120, 70],
      '55' : [60, 110],
      'b5' : [120, 50],
      '7' : [80, 90], 
      'b7' : [80, 70],
      '9' : [100, 70],
      '6' : [80, 50],
      '13' : [120, 110],
      '4' : [100, 130],
      '44' : [60, 70],
      '444' : [120, 30],
      '99' : [80, 150],
      'b3' : [100, 90],
      'b9' : [100, 50],
      'b6' : [120, 90],
      'b66' : [60, 130],
      'b55' : [60, 90]
    }
    for key, value in circles_root5.items():
        c.fill_style = 'black'
        c.begin_path()
        c.arc(value[0],value[1], 5, 0, 360)
        c.fill()
        c.close_path()
        c.stroke()

The image it draws should look like this:



Now we can work on drawing just the particular circles needed for any given guitar chord. Add the following line of code to the beginning of the program, right after the library imports:

chord = ['1', '3', '5', '11', '55']

Edit the 'for' loop which draws the circles, to include 2 'if' conditional evaluations:

for key, value in circles_root5.items():
  if key in chord:
    if key is '1':
      c.fill_style = 'white'
    else:
      c.fill_style = 'black'
    c.begin_path()
    c.arc(value[0],value[1], 5, 0, 360)
    c.fill()
    c.close_path()
    c.stroke()

What the code above does is draw only the circles for interval labels which are in the 'chord' list ('if key in chord:'). If the interval number is '1' (called the 'root note' of the chord in music theory), then the circle is drawn with white color. Otherwise, for any other interval, the circle is drawn black.

The full code only has a few additional lines, but we've already got a chord drawn:



Now we can add a dictionary of guitar interval definitions to the beginning of the program code, right after the library imports:

root5_shapes = {
  "major triad" : [['1', '3', '5', '11', '55', ], "."],
  "minor triad" : [['1', 'b3', '5', '11', '55', ], "m"],
  "power chord" : [['1', '55', ], "5"],
}

What the dictionary above does is associate a type of chord with a list of intervals found in the circles_root5 dictionary defined above. We can add the chord names to a drop_down widget on the design, in the __init__ method:

ddvalues = []
for key, value in root5_shapes.items():
  ddvalues.append(key)
  self.drop_down_1.items = ddvalues

and move the 'circles_root5' dictionary, plus the loop that draws the circles, to the 'drop_down_1_change' method definition (so the proper chord diagram is drawn whenever a chord type is chosen.)

def drop_down_1_change(self, **event_args):
  circles_root5 = {
    '1' : [40, 70],
    '11' : [80, 110],
    '111' : [100, 30],
    '3' : [100, 110],
    '33' : [60, 50],
    'b33' : [60, 30],
    '5' : [120, 70],
    '55' : [60, 110],
    'b5' : [120, 50],
    '7' : [80, 90], 
    'b7' : [80, 70],
    '9' : [100, 70],
    '6' : [80, 50],
    '13' : [120, 110],
    '4' : [100, 130],
    '44' : [60, 70],
    '444' : [120, 30],
    '99' : [80, 150],
    'b3' : [100, 90],
    'b9' : [100, 50],
    'b6' : [120, 90],
    'b66' : [60, 130],
    'b55' : [60, 90]
  }
  c = self.canvas_1
  c.stroke_style = "black"
  chord = root5_shapes[self.drop_down_1.selected_value][0]
  for key, value in circles_root5.items():
    if key in chord:
      if key is '1':
        c.fill_style = 'white'
      else:
        c.fill_style = 'black'
      c.begin_path()
      c.arc(value[0],value[1], 5, 0, 360)
      c.fill()
      c.close_path()
      c.stroke()

This will now draw the chosen chord from the drop_down_1 list. All that's left is to complete a full list of chord definitions for the 'root5_shapes', and duplicate the entire set of definitions for the 6th string. We also need to clear the screen and redraw the rectangle grid every time a new chord is chosen from the drop_down_1 list. We'll move that code into a 'clear_canvas' method definition:

def clear_canvas(self):
  c = self.canvas_1
  c.stroke_style = "black"
  c.clear_rect(0, 0, 400, 300)
  for i in range(20, 111, 20):
    for j in range(20, 141, 20):
      c.stroke_rect(i, j, 20, 20)
  for i in range(220, 311, 20):
    for j in range(20, 141, 20):
      c.stroke_rect(i, j, 20, 20)

We should run that method when the form is first shown, and also run the drop_down_1_change method, to display some starting chord diagrams:

def form_show(self, **event_args):
  self.clear_canvas()
  self.drop_down_1_change()

Here's the final code:

from ._anvil_designer import Form1Template
from anvil import *

root5_shapes = {
  "major triad, no symbol (just a root note)" : [['1', '3', '5', '11', '55', ], "."],
  "minor triad, min, mi, m, -" : [['1', 'b3', '5', '11', '55', ], "m"],
  "augmented triad, aug, #5, +5" : [['1', '3', 'b6', '11', 'b66', ], "aug"],
  "diminished triad, dim, b5, -5" : [['1', 'b3', 'b5', '11', ], "dim"],
  "power chord, 5" : [['1', '55', ], "5"],
  "sus4, sus" : [['1', '4', '5', '11', '55', ], "sus4"],
  "sus2, 2" : [['1', '9', '5', '11', '55', ], "sus2"],
  "major 6, maj6, ma6, 6" : [['1', '3', '55', '13', '11', ], "6"],
  "minor 6, min6, mi6, m6" : [['1', 'b3', '55', '13', '11', ], "m6"],
  "major 6/9, 6/9, add6/9" : [['1', '33', '6', '9', '5', ], "69"],
  "major 7, maj7, ma7, M7, (triangle) 7" : [['1', '3', '5', '7', '55', ], "maj7"],
  "dominant 7, 7" : [['1', '3', '5', 'b7', '55', ], "7"],
  "minor 7, min7, mi7, m7, -7" : [['1', 'b3', '5', 'b7', '55', ], "m7"],
  "half diminished, min7(b5), (circle w/ line), m7(-5), -7(b5)" : [
    ['1', 'b3', 'b5', 'b7', 'b55', ], "m7(b5)"],
  "diminished 7, dim7, (circle) 7" : [['1', 'b33', 'b5', '6', '111', ], "dim7"],
  "dominant 7 sus4, 7sus4" : [['1', '4', '5', 'b7', '55', ], "7sus4"],
  "dominant 7 sus2, 7sus2" : [['1', '9', '5', 'b7', '55', ], "7sus2"],
  "dominant 7 flat 5, 7(b5), 7(-5)" : [['1', '33', 'b5', 'b7', '111', ], "7(b5)"],
  "augmented 7, 7(#5), 7(+5)" : [['1', '33', 'b6', 'b7', '111', ], "7(+5)"],
  "dominant 7 flat 9, 7(b9), 7(-9)" : [['1', '33', '5', 'b7', 'b9', ], "7(b9)"],
  "dominant 7 sharp 9, 7(#9), 7(+9)" : [['1', '33', 'b7', 'b3', ], "7(+9)"],
  "dominant 7 b5 b9, 7(b5b9), 7(-5-9)" : [['1', '33', 'b5', 'b7', 'b9', ], "7(b5b9)"],
  "dominant 7 b5 #9, 7(b5#9), 7(-5+9)" : [['1', '33', 'b5', 'b7', 'b3', ], "7(b5+9)"],
  "augmented 7 flat 9, aug7(b9), 7(#5b9)" : [['1', '33', 'b6', 'b7', 'b9', ], "7(+5b9)"],
  "augmented 7 sharp 9, aug7(#9), 7(#5#9)" : [['1', '33', 'b7', 'b3', 'b6', ], "7(+5+9)"],
  "major add9, add9, add2" : [['1', '3', '5', '99', '55', ], "add9"],
  "minor add9, min add9, m add9, m add2" : [['1', 'b3', '5', '99', '55', ], "madd9"],
  "major 9, maj9, ma9, M9, (triangle) 9" : [['1', '33', '5', '7', '9', ], "maj9"],
  "major 9 sharp 11, maj9(#11), M9(+11)" : [['1', '33', 'b5', '7', '9', ], "maj9(+11)"],
  "dominant 9, 9" : [['1', '33', '5', 'b7', '9', ], "9"],
  "dominant 9 sus4, 9sus4, 9sus" : [['1', '44', '5', 'b7', '9', ], "9sus"],
  "dominant 9 sharp 11, 9(#11), 9(+11)" : [['1', '33', 'b5', 'b7', '9', ], "9(+11)"],
  "minor 9, min9, mi9, m9, -9" : [['1', 'b33', '5', 'b7', '9', ], "m9"],
  "dominant 11, 11" : [['1', 'b7', '9', '44', '444', ], "11"],
  "major 13, maj13, ma13, M13, (triangle) 13" : [['1', '3', '55', '7', '13', ], "maj13"],
  "dominant 13, 13" : [['1', '3', '55', 'b7', '13', ], "13"],
  "minor 13, min13, mi13, m13, -13" : [['1', 'b3', '55', 'b7', '13', ], "m13"]
}

root6_shapes = {
  "major triad, no symbol (just a root note)" : [['1', '3', '5', '11', '55', '111', ], "."],
  "minor triad, min, mi, m, -" : [['1', 'b3', '5', '11', '55', '111', ], "m"],
  "augmented triad, aug, #5, +5" : [['1', '3', 'b6', '11', '111', ], "aug"],
  "diminished triad, dim, b5, -5" : [['1', 'b3', 'b5', '11', ], "dim"],
  "power chord, 5" : [['1', '55', ], "5"],
  "sus4, sus" : [['1', '4', '5', '11', '55', '111', ], "sus4"],
  "sus2, 2" : [['1', '99', '5', '11', ], "sus2"],
  "major 6, maj6, ma6, 6" : [['1', '3', '5', '6', '11', ], "6"],
  "minor 6, min6, mi6, m6" : [['1', 'b3', '5', '6', '11', ], "m6"],
  "major 6/9, 6/9, add6/9" : [['1', '111', '3', '13', '9', ], "69"],
  "major 7, maj7, ma7, M7, (triangle) 7" : [['1', '3', '5', '7', '11', '55', ], "maj7"],
  "dominant 7, 7" : [['1', '3', '5', 'b7', '11', '55', ], "7"],
  "minor 7, min7, mi7, m7, -7" : [['1', 'b3', '5', 'b7', '11', '55', ], "m7"],
  "half diminished, min7(b5), (circle w/ line), m7(-5), -7(b5)" : [
    ['1', 'b3', 'b5', 'b7', '11', ], "m7(b5)"],
  "diminished 7, dim7, (circle) 7" : [['1', 'b3', 'b5', '6', '11', ], "dim7"],
  "dominant 7 sus4, 7sus4" : [['1', '4', '5', 'b7', '55', '11', ], "7sus4"],
  "dominant 7 sus2, 7sus2" : [['1', 'b7', '99', '5', '11', ], "7sus2"],
  "dominant 7 flat 5, 7(b5), 7(-5)" : [['1', '3', 'b5', 'b7', '11', ], "7(b5)"],
  "augmented 7, 7(#5), 7(+5)" : [['1', '3', 'b6', 'b7', '11', ], "7(+5)"],
  "dominant 7 flat 9, 7(b9), 7(-9)" : [['1', '3', '5', 'b7', 'b9', ], "7(b9)"],
  "dominant 7 sharp 9, 7(#9), 7(+9)" : [['1', '111', '3', 'b77', 'b33', ], "7(+9)"],
  "dominant 7 b5 b9, 7(b5b9), 7(-5-9)" : [['1', '3', 'b5', 'b7', 'b9', ], "7(b5b9)"],
  "dominant 7 b5 #9, 7(b5#9), 7(-5+9)" : [['1', '3', 'b5', 'b7', 'b33', ], "7(b5+9)"],
  "augmented 7 flat 9, aug7(b9), 7(#5b9)" : [['1', '3', 'b6', 'b7', 'b9', ], "7(+5b9)"],
  "augmented 7 sharp 9, aug7(#9), 7(#5#9)" : [['1', '3', 'b6', 'b7', 'b33', ], "7(+5+9)"],
  "major add9, add9, add2" : [['1', '3', '5', '999', '55', '11', ], "add9"],
  "minor add9, min add9, m add9, m add2" : [['1', 'b3', '5', '999', '55', '11', ], "madd9"],
  "major 9, maj9, ma9, M9, (triangle) 9" : [['1', '3', '5', '7', '9', ], "maj9"],
  "major 9 sharp 11, maj9(#11), M9(+11)" : [['1', '3', '7', '9', 'b5', ], "maj9(+11)"],
  "dominant 9, 9" : [['1', '3', '5', 'b7', '9', '55', ], "9"],
  "dominant 9 sus4, 9sus4, 9sus" : [['1', '4', '5', 'b7', '9', '55', ], "9sus"],
  "dominant 9 sharp 11, 9(#11), 9(+11)" : [['1', '3', 'b7', '9', 'b5', ], "9(+11)"],
  "minor 9, min9, mi9, m9, -9" : [['1', 'b3', '5', 'b7', '9', '55', ], "m9"],
  "dominant 11, 11" : [['1', 'b7', '99', '44', '11', ], "11"],
  "major 13, maj13, ma13, M13, (triangle) 13" : [['1', '3', '55', '7', '11', '13', ], "maj13"],
  "dominant 13, 13" : [['1', '3', '55', 'b7', '11', '13', ], "13"],
  "minor 13, min13, mi13, m13, -13" : [['1', 'b3', '55', 'b7', '11', '13', ], "m13"],
}

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    ddvalues = []
    for key, value in root5_shapes.items():
      ddvalues.append(key)
      self.drop_down_1.items = ddvalues

  def clear_canvas(self):
    c = self.canvas_1
    c.stroke_style = "black"
    c.clear_rect(0, 0, 400, 300)
    for i in range(20, 111, 20):
      for j in range(20, 141, 20):
        c.stroke_rect(i, j, 20, 20)
    for i in range(220, 311, 20):
      for j in range(20, 141, 20):
        c.stroke_rect(i, j, 20, 20)

  def form_show(self, **event_args):
    self.clear_canvas()
    self.drop_down_1_change()

  def drop_down_1_change(self, **event_args):
    circles_root5 = {
      '1' : [40, 70],
      '11' : [80, 110],
      '111' : [100, 30],
      '3' : [100, 110],
      '33' : [60, 50],
      'b33' : [60, 30],
      '5' : [120, 70],
      '55' : [60, 110],
      'b5' : [120, 50],
      '7' : [80, 90], 
      'b7' : [80, 70],
      '9' : [100, 70],
      '6' : [80, 50],
      '13' : [120, 110],
      '4' : [100, 130],
      '44' : [60, 70],
      '444' : [120, 30],
      '99' : [80, 150],
      'b3' : [100, 90],
      'b9' : [100, 50],
      'b6' : [120, 90],
      'b66' : [60, 130],
      'b55' : [60, 90]
    }
    circles_root6 = {
      '1' : [20, 70],
      '11' : [120, 70],
      '111' : [60, 110],
      '3' : [80, 90], 
      '33' : [40, 50],
      'b3' : [80, 70],
      '5' : [100, 70],
      '55' : [40, 110],
      'b5' : [100, 50],
      '7' : [60, 90],
      'b7' : [60, 70],
      '9' : [120, 110],
      '99' : [80, 50],
      '6' : [60, 50],
      '13' : [100, 110],
      '4' : [80, 110],
      '44' : [100, 30],
      '999' : [60, 150],
      'b77' : [100, 130],
      'b33' : [120, 130],
      'b9' : [120, 90],
      'b6' : [100, 90],
      'b55' : [40, 90]
    }
    self.clear_canvas()
    c = self.canvas_1
    c.stroke_style = "black"
    chord = root5_shapes[self.drop_down_1.selected_value][0]
    for key, value in circles_root5.items():
      if key in chord:
        if key is '1':
          c.fill_style = 'white'
        else:
          c.fill_style = 'black'
        c.begin_path()
        c.arc(value[0],value[1], 5, 0, 360)
        c.fill()
        c.close_path()
        c.stroke()

    chord = root6_shapes[self.drop_down_1.selected_value][0]
    for key, value in circles_root6.items():
      if key in chord:
        if key is '1':
          c.fill_style = 'white'
        else:
          c.fill_style = 'black'
        c.begin_path()
        c.arc((value[0] + 200), value[1], 5, 0, 360)
        c.fill()
        c.close_path()
        c.stroke()

  def link_1_click(self, **event_args):
    open_form('Form2')

The final app looks like this:







You can run a live version of this app at http://chords.anvil.app (There's an additional page of information in the app at this link which explains how the relevant music theory works to make up the interval patterns in these guitar chord diagrams).

23. Classified Ad Site - More About Navigation Layout and Relational Databases

This example demonstrates some key techniques which are useful in building larger applications. You'll see one way to organize form navigation, using the panels and pages to lay out multi-page apps. You'll also see how information in related database tables can be linked together and displayed in repeating panels, as well as some tidbits about database queries, server functions, and front-end code which you can use to perform common database and user management operations. We'll look at some fundamental features needed to build a classified ad/auction web site. The end result won't be a fully functional auction app, but some reusable scaffolding for many sorts of your potential future app designs.

23.1 Navigation

One of the first challenges you'll run into when developing larger apps with many forms, is how to organize navigation so that desired functionality can be easily found, and so that work flow progresses naturally. Grouping activities together categorically helps guide users through whatever series of processes they've come to the app to complete. On an auction site, for example some users may enter with the intention of buying, some may come to sell, and any of those users may come back to take part in different stages of each of those processes. Although it's possible to simply place a large number of links on a home page, organizing the site into grouped sections of pages can make it easier for users to find the functionality they need.

You've already seen how to open forms, and how to replace content panels to navigate between forms in Anvil. The basic idea in organizing larger work flows is to combine both those navigation techniques to create related content sections for your app.

To be clear, the default Anvil forms which you select when creating a new form, with headers and navigation links, we'll call 'pages'. Create a new Anvil app named 'Auction', and add 3 pages named 'Home_page', 'Buy_page', and 'Sell_page':



Blank forms without any navigation, we'll call 'panels'. Pages can be used to group together panels on any multi-page site. On our auction site, the 3 pages above will be used to group together panels related to selling, buying, and managing account settings. Add 10 new panel forms to your app, and drag them in the app browser so that they're nested as shown below (i.e., so that all the 'Home' panels are listed under the 'Home_page' package folder icon, the 'Sell' panels are grouped under 'Sell_page', and 'Buy' panels are under 'Buy_page'):



Now add the following link widgets to the Home_page form (you've seen how to do this in the previous tutorial sections about navigation). Set the 'visible' property of the 'messages' link to false (uncheck the checkbox):



Add the following code to Home_page:

from ._anvil_designer import Home_pageTemplate
from anvil import *
import anvil.server
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
from .Home_panel import Home_panel
from .Home_aboutus_panel import Home_aboutus_panel
from .Home_account_panel import Home_account_panel
from .Home_messages_panel import Home_messages_panel

class Home_page(Home_pageTemplate):
  def __init__(self, **properties):
    self.init_components(**properties)
    self.content_panel.add_component(Home_panel())
    if None != anvil.users.get_user(): self.logged_in()

  def link_buy_click(self, **event_args):
    open_form('Buy_page')

  def link_sell_click(self, **event_args):
    open_form('Sell_page')

  def link_about_us_click(self, **event_args):
    self.content_panel.clear()
    self.content_panel.add_component(Home_aboutus_panel())

  def link_messages_click(self, **event_args):
    self.content_panel.clear()
    self.content_panel.add_component(Home_messages_panel())

  def logged_in(self):
      self.link_login.text = 'Account'
      self.link_messages.visible = True
      self.content_panel.clear()
      self.content_panel.add_component(Home_account_panel())

  def link_login_click(self, **event_args):
    login = anvil.users.get_user()
    if None == login:
      if None != anvil.users.login_with_form(
        show_signup_option=True, 
        allow_cancel=True
      ): 
        self.logged_in()
    else:
      self.logged_in()

  def link_home_click(self, **event_args):
    open_form('Home_page')

Much of this code should be at least partially familiar to you. Let's go through each section briefly to point out key pieces of code. First, notice the import syntax: 'from .Home_panel import Home_panel'. When panels are grouped within page packages (nested visually in the app browser), they're referenced with a single dot.

Next, notice that the Home_panel is loaded and displayed in the __init__ method with self.content_panel.add_component(Home_panel()). The 'logged_in' method is also executed if the user is logged in. Jump down a few lines in the code and look at that method definition - it changes the text displayed by the link_login widget, sets the '.visible' property of the link_messages widget to true (so that it now appears in the header bar), and replaces the default Home_panel with Home_account_panel (so the home page displays different content when the user is logged in).

Next, notice that whenever the links for 'Buy' and 'Sell' are clicked, the user navigates to a whole new page display, using form_show(). When the links for 'About us' and 'Messages' are clicked, the page isn't changed, but the content panel is replaced (so the user still sees all the home page links - the main content area is just replaced with a different panel).

Finally, notice that when the login link is clicked, its click function goes through the process of logging in the user, with the option for new users to sign up for an account. If the user logs in, then the logged_in method is executed (same as in the __init__ method). This code is generally useful in many types of apps.

To make the user login routine work, you'll need to add the 'users' data service, and you should add the 'data tables' service while you're at it, since this app will access Anvil data tables.

Because we're just creating some generic app scaffolding, the content on each of pages will be left to some descriptive text:



You can see the rest of the page contents by running the live app at https://auction.anvil.app. The main thing to notice is that 'page' forms contain links to their grouped 'panel' forms, as well as a link back to the home page. This allows users to navigate easily between grouped sections of the app:



The code for each of the 'page' forms contains the same pattern to import its linked sub-panel forms, as well as code using open_form() to navigate to pages, and self.content_panel.add_component() to load and display panels, when links are clicked by the user. Here's the code for the 'Buy' page:

from ._anvil_designer import Buy_pageTemplate
from anvil import *
import anvil.server
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
from .Buy_panel import Buy_panel
from .Buy_RFQ_panel import Buy_RFQ_panel
from .Buy_auction_panel import Buy_auction_panel
from ..Home_page import Home_page

class Buy_page(Buy_pageTemplate):
  def __init__(self, **properties):
    self.init_components(**properties)
    self.content_panel.add_component(Buy_panel())
def link_home_click(self, **event_args):
  open_form('Home_page')

def link_auction_click(self, **event_args):
  self.content_panel.clear()
  self.content_panel.add_component(Buy_auction_panel())

def link_2_click(self, **event_args):
  self.content_panel.clear()
  self.content_panel.add_component(Buy_RFQ_panel())

def link_1_click(self, **event_args):
  self.content_panel.clear()
  self.content_panel.add_component(Buy_panel())

Here's the layout and code for the 'Sell' page:



from ._anvil_designer import Sell_pageTemplate
from anvil import *
import anvil.server
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
from .Sell_panel import Sell_panel
from .Sell_auction_panel import Sell_auction_panel
from .Sell_RFQ_panel import Sell_RFQ_panel
from ..Home_page import Home_page

class Sell_page(Sell_pageTemplate):
  def __init__(self, **properties):
    self.init_components(**properties)
    self.content_panel.add_component(Sell_panel())
def link_home_click(self, **event_args):
  open_form('Home_page')

def link_auction_click(self, **event_args):
  self.content_panel.clear()
  self.content_panel.add_component(Sell_auction_panel())

def link_rfq_click(self, **event_args):
  self.content_panel.clear()
  self.content_panel.add_component(Sell_RFQ_panel())

def link_1_click(self, **event_args):
  self.content_panel.clear()
  self.content_panel.add_component(Sell_panel())

There's no limitation upon which panels or pages can be opened by any other page or panel - for example, some account or messaging page could potentially be opened by any other Page or Panel at any time - just link to it and either open a new page or display a different panel on the current page. Pages and panels can be added and deleted from any other page whenever needed. Organizing things that way on the site just provides a simple visually cohesive navigation grouping ('areas' of the app), wherever you want such a grouping. (BTW, it's just as simple to nest pages within pages, and pages within panels, using self.content_panel.add_component()).

23.2 Using Linked Data Tables

Now let's look at the data tables we'll need in this app. The Users table is created automatically by the Anvil users service:



We'll save the data for auction listings and messages in 2 separate tables called 'Auction Items' and 'Messages':





Notice in the tables above that the columns containing email addresses are of type 'Users Row'. These rows were created by adding a 'linked' row from the Users table:



The idea of linking rows from separate database tables is important in the process of using 'relational' databases. It allows you to save information in one table and access that data when querying results from another table, just by including such a link. You can see how this is useful by taking a look at the Home_Messages_Panel form:



When this panel is loaded (from the link on the home page), its __init__ method calls a server function, which sets the '.items' property of the repeating panel widget on the page:

def __init__(self, **properties):
  self.init_components(**properties)
  self.repeating_panel_1.items = anvil.server.call('get_messages')

The server function loads some data from the 'messages' database table, using a query that returns any rows in which the User_From or User_To columns match the currently logged in user:

@anvil.server.callable
def get_messages():
  userrow=anvil.users.get_user()
  return app_tables.messages.search(
      q.any_of(
        User_From=userrow,
        User_To=userrow
      )
  )

That function returns a Python dictionary containing rows from the 'messages' table which have messages to or from the logged in user. Notice that nowhere in the 'message' table is the user's name listed in any column (only the email address from their user account is found in the message table). But when we display messages in the repeating panel user interface, we don't want to display their account email address - we simply want to show their name. This is where the idea of a linked table becomes useful. In the repeating panel template, we can set the data binding for the label_3 widget to self.item['User_From']['name']:



Remember, the '.items' property of this repeating panel has been set to a dictionary containing results from the server database query. So, setting the data binding of the label_3 widget on each repeating row to self.item['User_From'], for example, refers to the row from the Users table, which has been linked in the Messages table. Because the data returned from the database query is a Python dictionary, the ['name'] key following self.item['User_From'], refers to the name column in the linked Users table. If this seems a bit confusing, just remember that this sort of generic data binding will be used often when getting values from linked tables: self.item['linked_table_row']['column_name']. Just memorize that code pattern for now and recognize what it does (it gets values from linked database tables into a user interface).

The resulting page display looks like this:



One thing to note from the process above is that the following query in the get_messages server function returns only the rows we want from the database (those messages from or to the current user):

return app_tables.messages.search(
      q.any_of(
        User_From=userrow,
        User_To=userrow
      )
)

So that's going to be much more performant than doing something like:

for row in app_tables.messages.search():
  #do something to/with each of these rows

Browser code performs more slowly than server code in general, so optimizing speed for the front end will typically involve returning more refined values from search queries on the server. The exception would be if you want take the hit on front end performance to cut down on server load, but most apps you make won't see the volume of traffic that require those sorts of optimization design choices (if so, you likely have a good problem, with many many many users of your app, and hopefully lots of income being generated to pay for design improvements).

23.2.1 Another Linked Data Table

There's a similar routine used in the data binding for the widgets of the repeating panel on the Buy_auction_panel form. When the form loads, the '.items' property of the repeating panel on the page is set to the data returned by the 'get_auctions' server function:

def __init__(self, **properties):
  self.init_components(**properties)
  self.repeating_panel_1.items = anvil.server.call('get_auctions')

Here's the server function called above - it just returns all the rows in the auction_items table:

@anvil.server.callable
def get_auctions():
  return app_tables.auction_items.search()

In the template for the repeating panel row, the data binding for the label_5 widget, for example, is set to self.item['Seller']['name']. This gets the seller's name from the user account linked in the 'seller' column of the auction_items table:



Notice that all the other data bindings simply refer to columns in the auction_items table:





There's one more nugget to catch in this code: image widgets in the Buy_auction_panel repeating panel display are hidden when no image is included in the auction entry. This is handled in the __init__ code of the Buy_auction_panel:

def __init__(self, **properties):
  self.init_components(**properties)
  if self.item['Image'] != None:
    self.image_1. visible = True

23.3 Adding Related Rows to the DB, from UI Widgets

Here is the design layout for the Sell_auction_panel form:



The code for this form contains the basic syntax for calling a server function and passing argument values from UI widgets, to be added to the database. Pay particular attention to the third to last line of the server function call - that's where the linked row from the Users table gets added to the seller column of the auction_items table. Also notice that some validation of the image_1.source data has been added, using an 'if' conditional evaluation:

def button_1_click(self, **event_args):
  if self.image_1.source.content_type == 'text/html; charset=utf-8':
    my_image = None
  else:
    my_image = self.image_1.source
  anvil.server.call(
    'add_auction',
    self.text_box_1.text,
    self.text_area_1.text,
    datetime.now(),
    self.date_picker_1.date,
    self.text_box_2.text,
    anvil.users.get_user(),    # THIS IS WHERE THE LINKED ROW IS ADDED
    my_image,
    False
  )
  alert("Added!")
  open_form('Sell_page')

Here's how the server function inserts the argument values into the database, using Anvil's add_row() ORM function:

@anvil.server.callable
def add_auction(title, description, start_date, end_date, minimum_bid, seller, image, complete):
  app_tables.auction_items.add_row(
    Title=title,
    Description=description,
    Start_Date=start_date,
    End_Date=end_date,
    Minimum_Bid=float(minimum_bid),
    Seller=seller,
    Image=image,
    Complete=complete
  )

And with that, we've got a working set of tools to add linked rows of data to a relational database, to query results from linked rows, and to display results in a user interface.

The patterns you've seen in this section of the tutorial can be a bit confusing at first, but they're absolutely worth learning. The generic page/panel design used to group forms together in larger user interface layouts, and saving/retrieving data to/from multiple related tables of data are fundamental building blocks in many common and useful types of apps.

You can see this app scaffolding run live at https://auction.anvil.app and clone a copy of the code at https://anvil.works/build#clone:KERBX5B2H4BIFBW5=TF53UAXKRV55Q6RLO4R7LKOQ

24. Uplink App - Accessing an Sqlite Database on an Android Phone with Anvil

24.1 What is Uplink?

You've already seen how web APIs enable programs of all types to share data and computation results across disparate hardware, operating systems, and programming language platforms. In today's world, such APIs are enabling greater and greater connected computing functionality. Anvil's 'uplink' feature enables even more flexible connected computing capabilities, with a method that is far simpler to implement, and potentially more powerful.

Uplink allows Anvil apps to connect with any other device that is able to run Python code, and execute shared Python functions on those machines. Imagine building a robot that runs on a Raspberry Pi computer. You can write functions that run on the Raspberry Pi computer, and share them with an Anvil app, using uplink. The functions that live on the robot's hard drive and execute on the Raspberry Pi CPU, can be executed by your Anvil app, just as if they are running on the Anvil server. What that means for you and robotic programming, for example, is that you can control the robot's functionality, directly via a user interface created purely in Anvil. No web API or other complex network connectivity protocols are required.

Or if you're a data scientist, you may do iterative work in Jupyter notebooks, writing functions which train machine learning libraries to perform artificial intelligence tasks. Instead of having to port all of your completed work from your Jupyter notebook scratchpad, to a web server computer, re-implement an environment with all the same libraries and data files installed in your notebook, and incorporate salient pieces of your notebook code within a web development framework such as Django, using HTML, CSS, and JavaScript, just to build a front end - and along the way, write SQL queries to save/retrieve everything from a database, write functions to convert and transfer those data results into Json data structures that can be passed back and forth between front-end and back-end languages, debugging the entire process for hours... Instead of all that, you can write a single line of code in your Jupyter notebook, to connect all the existing work that you've completed there, and run any of the functions in that notebook directly in an Anvil app. Uplink can save you tremendous volumes of work in that way.

Anvil servers come with a large majority of the most common libraries installed and ready to go, so you can just 'import library-name' and start using the functions from a huge percentage of popular libraries. But there are literally millions of libraries available at https://pypi.org/ . Those libraries keep you from having to re-create the wheel, in just about any sort of computing task you may want to achieve. Anvil Uplink allows you to install any libraries you want, on just about any kind of computer you want, and run those privately installed libraries, on the computers you own/control - as if they're installed on Anvil server machines. This allows you to set up server programs, which you have total freedom to implement and control, on computer hardware that you have unlimited access to, and which can do absolutely anything which is possible using the Python ecosystem. And you can connect Python functions on these systems, to your Anvil app, with literally one line of stock code.

Part of the reason that Python has become so popular, is that is supports so many different sorts of computing activities. There are Python libraries and tools which make scientific computing, web development, 'maker' hardware control, and virtually every other technological task much easier to complete. In many cases, Python has best-of-breed tooling available for completing industrial quality server computing tasks, in ways which would be next to impossible in other programming languages. Python runs natively in just about every imaginable environment, on every common operating system, and on every kind of mainstream hardware, from tiny embedded microcontrollers to commercial servers to browsers on the front end. Anvil's Uplink allows you to connect code on any of those sorts of systems, with basically no effort whatsoever. This sort of simplicity is unheard of, using any other tool.

24.2 Just 'pip install anvil-uplink'

In order to use Uplink, it's necessary to understand a bit about Python's 'package management system' called PIP. PIP comes as a default part of any modern installation of Python, as part of the 'batteries included' standard library and toolset that you get when you install a mainstream distribution of Python on your computer. To install any library found at pypi.org, just go to your operating system console command line ('cmd' in Windows), and type 'pip install name-of-library'. PIP handles installing any dependencies required to get the desired library running. There is a lot to learn about managing versions of libraries in 'environments', but that's out of the scope of this tutorial. The main thing to understand with PIP, is that you can install powerful interfaces to third party computing resources (for example web APIs, AI libraries, UI, game, and much more), just with 'pip install'.

To configure Uplink, create a new app in Anvil, click the 'gear' icon in the App Browser, and select 'Uplink...':



In the Uplink dialogue, click 'Server' and then copy the 'Uplink Key':



You'll see some instructions in the Uplink dialogue, which explains how to use the uplink in code on a remote computer. On your computer (separate from Anvil), just type 'pip install anvil-uplink' at your computer's system console.





Then, create a new text file with the following Python code:

import anvil.server

@anvil.server.callable
def writedata(received_text):
  f = open("uplinkfile.txt", "a")
  f.write(received_text)
  f.close()

anvil.server.connect("ORSN36YCL2KAKG7SHKKVSNCP-7IAPA3BOGKEGIYPJ")
anvil.server.wait_forever()

Let's look at the code above, line by line. The first line imports the anvil library needed to make the uplink connection.

The second line is the stock '@anvil.server.callable' decorator which is placed before any function you want to use in your Anvil app.

The next 4 lines make up a function definition which appends the data argument of the 'writedata' function, to a file on the computer's hard drive (you can Google 'python write to file' to learn more about how this works).

The last 2 lines connect to your Anvil app and wait for it to run the 'writedata' function. Replace the uplink key in the 2nd to last line, with the uplink key that Anvil generated for you in your uplink dialogue.

Save that code to a file named 'writedata.py' (or to any other name you want), and run it on your computer (this typically involves changing directories to the folder where you saved the file (with 'CD' at the command line), and then typing 'python writedata.py' (or whatever you named the python program)):



Now drag a single TextBox onto the Form1 design of your Anvil app, double-click the TextBox, and edit the 'text_box_1_pressed_enter' method so that it calls the 'writedata' function above, which is running on your computer:

anvil.server.call("writedata", self.text_box_1.text)

Your entire Anvil program should look like this:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.server

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def text_box_1_pressed_enter(self, **event_args):
    anvil.server.call("writedata", self.text_box_1.text)

Now run your Anvil program, enter some text into the TextBox, and you'll see every line entered into the Anvil app gets saved to the 'uplinkfile.txt' file on your local computer's hard drive:





To be clear, you can publish the Anvil app above, and any person who uses that app - in any modern web browser, on any computing device anywhere in the world - can save any lines of text they type into the TextBox, directly to 'uplinkfile.txt' on your home computer. Of course, this is the simplest possible code example, but perhaps that sheds an inkling of understanding about what is potentially possible using Anvil's extremely powerful Uplink capability.

That's the entire basic process which is needed to connect any Anvil app to any Python program running on any computer anywhere.

For more Uplink documentation, tutorials, and code examples, Google 'anvil python uplink'. There is much more to learn about it at the official anvil.works web site.

24.3 Porting Anvil's PostgreSQL Uplink Example to Sqlite Running on my Cell Phone

NOTE: This tutorial assumes some understanding of database systems and SQL (the query language used in most traditional databases), which is out of the scope of this tutorial.

The tutorials at anvil.works include a detailed example which demonstrates how to connect an Anvil front-end to a PostgreSQL database script running on a desktop or server computer: https://anvil.works/learn/tutorials/external-database . This is a great tutorial, but it requires a connection to a PostgreSQL database system, which is a very large piece of software to install.

'Sqlite' is a much smaller and more common database system which comes by default with every mainstream installation of Python. Sqlite runs on many tiny devices, and comes by default as part of most web browser installations, even on low powered cell phones. Since Sqlite can run virtually everywhere, this example demonstrates how to convert the version of SQL which is used in PostgreSQL, to the version of SQL which runs in Sqlite. The full app in this example includes uplink code which can run on your home computer or cell phone, along with an Anvil app which connects to that code, to enter, edit, and delete rows of data in the database.

This first script can be used to create the sqlite database on your device:

import sqlite3
connection = sqlite3.connect("inventory2")   # creates an inventory2.db file
cur = connection.cursor()
cur.execute(
  "create table inventory (id integer PRIMARY KEY AUTOINCREMENT, item_name text, quantity text)"
)

my_inventory_data = [
  (1, 'Vase', 10),
  (2, 'Bookcase', 5),
  (3, 'Bathtowel', 20),
  (4, 'Frying Pan', 20),
  (5, 'Large Saucepan', 25),
  (6, 'Small Saucepan', 25),
  (7, 'Dinner Plate', 15),
  (8, 'Dining Chair', 10)
]

cur.executemany(
  'insert into inventory values (?,?,?)', my_inventory_data
)
connection.commit()
for row in cur.execute('select * from inventory'):
  print(row):
connection.close()

These lines, ported to Sqlite from the original tutorial, cause threading issues in any function with an open sqlite db:

cur.execute('SELECT id, item_name, quantity FROM inventory;')  
items = cur.fetchall()

To fix the sqlite threading issue, every time you want to execute the code above, the connection to Sqlite needs to be closed and reopened *within each function* that executes SQL, then committed and closed when done.

Here's the final code. You should be able to run this on any computer with a current version of Python 3 installed (and the anvil-uplink library PIP installed):

import sqlite3
import anvil.server

anvil.server.connect("E3JZYP2SLIWZAKHILOFKSJZP-HO2WCSYIJUIVBX2M")

@anvil.server.callable
def get_items():
  connection = sqlite3.connect("inventory2")  
  cur = connection.cursor()                   
  cur.execute('SELECT id, item_name, quantity FROM inventory;')
  items = cur.fetchall()
  return [
    {'id': item[0], 'name': item[1], 'quantity': item[2]}
    for item in items
  ]
  connection.close()

This code from the PostrgreSQL tutorial:

cur.execute(''' INSERT INTO inventory (item_name, quantity) VALUES (%s, %s); ''',
[name, quantity])

Is altered below for sqlite:

@anvil.server.callable
def insert_item(name, quantity):
  connection = sqlite3.connect("inventory2")  # sqlite threading fix 
  cur = connection.cursor()                   
  cur.execute(
    ''' INSERT INTO inventory (item_name, quantity) VALUES (?, ?); ''', 
    [name, quantity]
  )
  connection.commit()
  connection.close()

@anvil.server.callable
def update_item(item_id, name, quantity):
  connection = sqlite3.connect("inventory2")  # sqlite threading fix 
  cur = connection.cursor()  
  cur.execute(''' UPDATE inventory SET item_name = ?, quantity = ? WHERE id = ?; ''', 
  [name, quantity, item_id])
  connection.commit()
  connection.close()

@anvil.server.callable
def delete_item(item_id):
  connection = sqlite3.connect("inventory2")  # sqlite threading fix 
  cur = connection.cursor()       
  cur.execute(''' DELETE FROM inventory WHERE id = ?; ''', [item_id])
  connection.commit()
  connection.close()

print(get_items())

anvil.server.wait_forever()

Remember, you must run the uplinked code above on your computer, a phone, or some other device that can run Python, and change the uplink key to match the key generated by your uplink dialogue.

Now here's the Anvil app design (basically as described in the PostrgreSQL tutorial at anvil.works):







Here's the code for Form1:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.server

class Form1(Form1Template):

  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    # self.repeating_panel_1.items = anvil.server.call('get_items')
    self.refresh()
    self.set_event_handler('x-refresh', self.refresh)

  def refresh(self, **event_args):
    self.repeating_panel_inventory.items = anvil.server.call('get_items')

  def button_add_click(self, **event_args):
    """This method is called when the button is clicked"""
    anvil.server.call(
      'insert_item',
      self.text_box_name.text,
      self.text_box_quantity.text
    )
    get_open_form().raise_event('x-refresh')

    # Clear the input boxes
    self.text_box_name.text = '' 
    self.text_box_quantity.text = ''

And here's the code for RowTemplate1:

from ._anvil_designer import RowTemplate1Template
from anvil import *
import anvil.server

class RowTemplate1(RowTemplate1Template):
  def __init__(self, **properties):
    self.init_components(**properties)

  def update_item(self, **event_args):
    anvil.server.call(
      'update_item',
      self.item['id'],
      self.text_box_name.text,
      self.text_box_quantity.text,
    )

  def button_delete_click(self, **event_args):
    anvil.server.call('delete_item', self.item['id'])
    get_open_form().raise_event('x-refresh')

Here is the app running, connected to an sqlite backend running in the 'Pydroid 3' Android app on my old Samsung J3 Orbit phone. The exact same back end code can also run on any of my laptop PCs with Python installed. That's pretty. darn. cool. Anvil:









You can run this app at https://sqlite.anvil.app (but you'll only get data if I have the uplinked functions running on my phone - you're going to have to implement this one yourselves to see it work).

25. Learning To Think In Code

By going through the examples so far, you've seen many essential pieces of the Anvil user interface system, the built-in database system, API functions, and services such as user management, as well as core parts of the Python language (variables, conditional evaluations, loops, method definitions, etc.). That's great, but you may still be asking yourself "ok, but how do I create a complete app that does ______, from scratch?".

To begin building your own apps from the ground up, a few routine thoughts about where to begin, and how to break down the process into manageable chunks, can help you get started. Learning to "think in code" - decomposing thoughts about what a program should do for a user, and organizing bits of Anvil API and Python code into familiar structures to satisfy those requirements - will become more natural with each project you complete.

This tutorial section intends to provide some general understanding about how to convert vaguely conceived design concepts into eventual final working code, and how to organize your work process for each unique situation with some methodic guidance. We'll go through multiple case studies to provide insight as to how specific project requirements were satisfied for different apps, from conception to completion.

25.1 A General Approach, Using Outlines and Pseudo Code

Software virtually never springs to life in any sort of initially finalized form. It typically evolves through multiple revisions, and usually develops in directions originally unanticipated. There's no perfect process to achieve final designs from scratch, but having a plan of attack to get started writing line 1 of your code is what eventually delivers a working piece of software to users' machines. Here's one possible general procedure to consider:

  • Start with a detailed definition of what the application should do in natural language. You won't get anywhere in the design process until you can describe some form of imagined final program. Explain your conceived app, with details about what the program interface should look like, how the user will interact with it, and what sort of data will it take in, process, and return.
  • Determine a list of design elements, code fragments, and data structures related to each of the program characteristics above. Take stock of any general UI design features and widgets that relate to what the program should look like. Imagine any code fragments which might run when the user interacts with the design. And, think about the variables, list structures, and database columns which can be used to store and manipulate data values in the app.
  • Begin writing a code outline. It's often easiest to do this by actually implementing a user interface (dragging widgets onto a form to create a design layout). A flow chart of operations can be helpful too, if your program is a web API or some other app without a graphic UI. The idea is to begin instantiating some code containers for your working program. At this point, the outline can be filled with simple natural language 'pseudo code' comments that simply describe what actual code needs to accomplish. Starting with a user interface outline is especially helpful because it provides a starting point to write actual code structure, in methods attached to each of the UI design elements. This step can potentially be replaced by naming server functions filled with pseudo-code descriptions, if there is no UI design. Creating a design layout also forces you to deal with how the program will specifically handle input, manipulation, and output of data. Structures such as methods which perform some function when a button is clicked, can be a tremendous help in getting you to think about the code logic you need to write, while starting to build a visual interactive layout that elicits details about what its computational guts need to accomplish. To flesh out this step, you can put pseudo-code comments in the event methods of each design widget - something like:
def button_1_click(self):
  # Assign a variable to the text entered by the user into text_box_1.
  # Append that value to the 'inventory' list.
  # Use a loop to update each item in the list with the selected value
  #   in the drop_down_1 list.
  # (etc.)
  • Finally, move on to replacing pseudo-code with actual working code. This isn't nearly as hard once you've completed the previous steps. Using tutorial code examples, the Anvil Docs, Anvil API reference, and Google can be a tremendous help. Once you're really familiar with all the available constructs in the language, and have more experience using them, all you'll likely need is an occasional syntax reminder from Anvil's autocomplete feature. Much of the real work along the way will have to do with learning to use functions in the numerous libraries available in the Python ecosystem, and how to assemble that functionality in a logical way. Just always keep in mind, with Python, you can nearly always find solutions explained online, with a Google search. Eventually, you'll pass through the other design steps more intuitively, and get through this stage quickly, thinking more directly in code, without the need for as much planning.
  • As a last step, debug your working code and add/change/refactor functionality while you test and use the program. This can end up being the most time-consuming cycle of the development process, especially when users provide feedback that requires changes to your original design.

The basic plan is to explain to yourself what the intended program should do, in some natural language terms, and then think through how all required user interface widgets and code structures must be organized to accomplish that goal. As an imagined program takes shape, organize your work flow using a top down approach: imagined concept -> general UI outline -> thought process and pseudo code methods -> working code -> tested, debugged, finished details.

25.1.1 Some More Thoughts About How To Approach the Development Process

Think of how an imagined program will flow from one operation to another - especially in terms of the order a user needs to interact with each UI design element. For each of those step-wise interactions, think about the order of events which need to be executed by methods and other separate blocks of code. For example, perhaps the user needs to enter a request in a text field, and then a database search needs to be performed, and then the returned data needs to be displayed in a grid, and then the user will need to select one of the fields from the displayed database rows, and then a computation needs to be performed upon the selected value... etc. Consider every action which is intended to happen in the imagined piece of software, and start thinking, "_this_ is how I could potentially accomplish _that step_, with Anvil UI elements and Python code..."

Focus on how the user will get data into and out of the app, and what the code needs to do with that data to make your program perform its useful task. Will the user enter information into text boxes, or pick values from drop_down lists, or click a button to begin a computation process? Will you need to use repeating panels to display rows of data values? Consider how the data used in the program can be represented in code, organized, and manipulated. What types of data will be involved: little text values such as dates, times, or URLs, paragraph-long text strings, binary types such as images and sounds, etc. What sorts of design widgets are associated with those sorts of data values? Will the program code potentially need to use loops to go through lists of values and do something to/with each value? Will you need to perform searches upon fields in each row of a database table, and perhaps perform 'if' evaluations upon each item in the resulting list? Will a timer need to execute periodically? And so on...

The majority of code you write will flow from one user input or data state to the next. Begin mapping out all the things that need to happen in the program, and the info that needs to be manipulated along the way, in order for those user interactions and data transformations to occur, from the beginning to the end of the user experience. Think of how the program must begin, and what must be done before the user starts to interact with the application (library imports, data that needs to be defined before the program starts, __init__ definition, etc.). Determine if any data needs to be stored in a database, or perhaps just in some variables in the initialization code. Then think of what must happen to accommodate each possible interaction the user might choose, or the results of some conditional evaluation. In some cases, for example, all possible actions may occur as a result of the user clicking/interacting with various UI widgets. If your program has no UI interface, for example, simply running in the background to connect with external data sources to provide some automated data processing, then perhaps you need to consume a web API, or design server functions that other services can consume as an API your app exposes. Perhaps you'll need to use uplink functionality to interact with functions running on another machine to change the state of a remote machine, or for that machine to change the state of some data your user inputs. Hopefully, some code from the example applications in this and other tutorials will come to mind, and you can begin to form a coding structure that enables the general work flow.

Sometimes it's simpler to begin thinking through a development process using bits of code you find in Python examples online. It's very likely that parts of what your code must accomplish have already been written by others in Python, and posted someplace online where they can be found by a Google search. You may just have to refactor some publicly available chunks of code, and integrate them within UI widget methods, as you've seen in several examples in this tutorial. Sometimes it helps to build your app around Python console interactions, replacing 'input' functions with pop-up requestors and/or input widgets.

Whatever your conceived interface, think of all the choices the user can make at any given time, and provide a user interface component to allow for those choices. Alternately, if you're writing an app without a user interface, think about all the possible states the data your app processes could be in. Then think of all the operations the computer must perform to react to each user choice or data state, and start by imagining, describing, and decomposing what must happen in the code, to perform those operations.

Whenever you have trouble tackling the process of writing lines of working Python/Anvil code, use some natural language pseudo-code to organize your thoughts, and formulate a Google search to satisfy that goal. Initially, just write a description of what you want a button to do. As you flesh out your outline, think through and describe the language elements and coding logic you conceive to perform various actions or to represent various data structures. The point of writing pseudo-code is to keep clearly focused on the overall design of the program, at every stage of the development process, without getting lost in the nitty gritty syntax details of actual code. It's easy to lose sight of the big picture whenever you get involved in writing each line of code, especially in the beginning. You'll get much better at writing code intuitively, as you become familiar with code examples, and fluent with refactoring common bits and pieces for other purposes.

As you convert pseudo-code thoughts to language syntax, remember that many actions in a program will occur as a result of conditional evaluations (if this happens, do this...), loops (do this to to every item in a list), or linear flow from one action to the next (call this method from that method). If you're going to perform certain actions multiple times or cycle through lists of data, you'll likely need to run some loops. If you need to work with changeable data, you'll need to define some variables, and you'll probably need to pass them as argument values to methods, in order to process the data. Think in those general terms first. Create a list of data values and method definitions that are required, and put them into a layout that makes the program structure flow from one definition, condition, loop, UI widget and/or data state, etc., to the next.

Playing with code examples, experimenting, refactoring, debugging and integrating incremental solutions in your apps can be frustrating at times, but it can also be an extremely satisfying, encouraging, and rewarding creative process, along the way to improving your confidence and valuable skills.

What follows are a number of case studies that describe how various programming tasks have been approached from conception and design, to working applications. Each example traces the train of thought from organizational process through completed code.

26. Case Studies

26.1 Markdown Editor

I want to create an app to edit and view documents using 'Markdown' language. Markdown is a very simple language used to create rich documents (with images, layout formatting, links, etc.), using readable codes that are inserted directly within a plain text file. It's similar in concept to HTML, but simpler in scope, less verbose, and more legible in source form. If you've never seen Markdown documents, take a look at www.markdownguide.org/getting-started to see what they are. I'll use the process outlined in the previous section, to work out how to build it.

  • Natural Language Description: The app should allow me to type in (or copy/paste), edit, save, reload and preview documents, written using Markdown code. So, how might a design interface that enables this sort of interaction and output look? ... Well, there needs to be a text entry area that allows the user to enter/edit text input (Markdown code), and there needs to be a display which shows the rendered document (the nicely formatted view created by the entered Markdown code). There also needs to be a place for the user to enter a unique name to save each Markdown document, plus some list display that allows the user to select and reload saved Markdown documents. Those documents also need some mechanism to be stored (some place to be saved and retrieved).
  • Design Components: A text_area widget will work well to allow users to enter/edit Markdown code. Anvil rich_text widgets have the built-in ability to display rendered Markdown code, so one of them will provide a drop-in solution for displaying the rendered Markdown document. A text_box widget can be used to enter a file name to save each document. A drop_down widget can be used to allow the user to select and reload from a list of saved documents. A database table can be used to save and retrieve the documents. I can imagine needing variables to store the Markdown code, as well as a list data structure to store names of saved files.

Ok let's drag and drop the widgets we've described above onto a new Anvil app design:



And let's create a database with columns for filename and Markdown code (this is a simple personal tool for in-house use, so we won't worry about secure server access to the database at this point):



We can add some pseudo-code at the start of the program, and in the methods for each widget, to begin to form a code outline:

def __init__(self, **properties):
  # At the start of the program, load the filenames of the saved Markdown
  # documents, into the drop_down_1 widget list.  We'll need to search the
  # database to retrieve this list.

def text_area_1_change(self, **event_args):
  # Whenever we edit the Markdown code in this widget, the display of the
  # rich_text_1 widget should be updated to show the changes.

def drop_down_1_change(self, **event_args):
  # We need to go through each row of the database, and if the 'filename'
  # field of the current row matches the selected item in drop_down_1,
  # then set the '.text' property of self.text_area_1 to the data in the
  # 'markdown' column of the current row.  This will load the saved
  # document code into the text area, where it can be edited.  Also,
  # set the filename in text_box_1 to the selected filename, where it
  # can be used to save the code changes back to the database, or edited
  # to create a new file, if desired.

def text_box_1_pressed_enter(self, **event_args):
  # When the user enters a new filename, we want to search the database
  # to see if the filename already exists.  If so, save the current 
  # markdown code in text_area_1 to the 'markdown' column in the found 
  # row (this overwrites the previous markdown code with the newly edited
  # code).  If the filename doesn't exist in the database, then add a 
  # row to the database with the new filename in text_box_1, and the 
  # markdown code in text_area_1.  We should also refresh the items in
  # the drop_down_1 list, to show any newly added filename.

Ok, so let's dive into replacing all the pseudo-code above with working Python code, starting with the __init__ method. You've seen a list comprehension in previous tutorial examples which performs exactly the search required in our code description. Just set the 'items' property of the drop_down_1 widget to the search results:

self.drop_down_1.items = {
  filenames['filename'] for filenames in app_tables.documents.search()
}

Now for the text_area_1_change method - this one is super easy. Just set the '.content' property of the rich_text_1 widget to the updated code in the text_area_1 widget, and the rich_text widget does the job of updating the displayed document rendering:

self.rich_text_1.content = self.text_area_1.text

The drop_down_1_change method needs to go through each row of the database, so we'll use a 'for' loop.

for row in app_tables.documents.search():

For each row, we need to perform an 'if' evaluation to see if the 'filename' field matches the selected value in drop_down_1:

if row['filename'] == self.drop_down_1.selected_value:

If so, set the text_area_1 text to the data in the 'markdown' column of the current row, and set the text_box_1 text to the 'filename':

self.text_area_1.text = row['markdown']
self.text_box_1.text = row['filename']

That wasn't too bad at all! Here's the whole method definition:

for row in app_tables.documents.search():
  if row['filename'] == self.drop_down_1.selected_value:
    self.text_area_1.text = row['markdown']
    self.text_box_1.text = row['filename']

Finally, for the text_box_1_pressed_enter method, we're going to use the text from text_box_1 several times, so let's assign it a variable label:

file = self.text_box_1.text

Now we need to perform an 'if' evaluation to see if the filename exists in our data table. Don't fret, every bit of code we need to interact with the database can be found at https://anvil.works/docs/data-tables/data-tables-in-code . This line checks to see if the value of the 'file' variable is found in the 'filename' column of the database:

if app_tables.documents.get(filename=file):

If the filename is found, replace the 'markdown' column of that row, with the text in text_area_1. The code to do exactly that can be found again at https://anvil.works/docs/data-tables/data-tables-in-code :

app_tables.documents.get(filename=file)['markdown'] = self.text_area_1.text

If the filename is not found in the database, then create a new row, filling the 'filename' column with the text from text_box_1, and the 'markdown' column with the text from text_area_1. The code to do exactly that can also be found at https://anvil.works/docs/data-tables/data-tables-in-code :

else:    
  app_tables.documents.add_row(
    filename=file, 
    markdown=self.text_area_1.text
  )

To refresh the drop_down_1 list, just run the exact same code that we created for the __init__ function:

self.drop_down_1.items = {
  filenames['filename'] for filenames in app_tables.documents.search()
}

With the help of the documentation at https://anvil.works/docs/data-tables/data-tables-in-code , none of that was difficult at all, and the pseudo-code reads almost like real code. We just needed to convert the logic into an if-else structure. Here's the full method definition:

file = self.text_box_1.text
if app_tables.documents.get(filename=file):
  app_tables.documents.get(filename=file)['markdown'] = self.text_area_1.text
else:    
  app_tables.documents.add_row(
    filename=file, 
    markdown=self.text_area_1.text
  )
self.drop_down_1.items = {
  filenames['filename'] for filenames in app_tables.documents.search()
}

I run that code to test it, and everything works, but there's something in the logic that strikes me. When the condition 'if app_tables.documents.get(filename=file)' is executed, the entire database is searched to determine if the 'file' name exists in the 'filename' column. Then, on the next line, if the file has been found, the entire database is searched immediately again, in order to determine which row the file is found in. That's some unnecessary extra work for the server. We can eliminate that second search by saving the results of the first search to a variable:

found_file = app_tables.documents.get(filename=file)
if found_file:
  found_file['markdown'] = self.text_area_1.text

The database in this app probably won't ever get so big, to bog down its operation with a second search. In apps with larger databases, however, performing a duplicate search could cause a noticeable delay for the user, and the duplicate search requires unnecessary extra work for the server. An important part of your job as a developer is to eliminate 'costly' work loads for the CPU, such as multiple unnecessary searches or loops within loops, that require many repeated computations by the computer. You should simplify and save 'cached' results of costly operations, as above, whenever possible in your code. With our newly refined search routine, here's the final text_box_1_pressed_enter method definition:

file = self.text_box_1.text
found_file = app_tables.documents.get(filename=file)
if found_file:
  found_file['markdown'] = self.text_area_1.text
else:    
  app_tables.documents.add_row(
    filename=file, 
    markdown=self.text_area_1.text
  )
self.drop_down_1.items = {
  filenames['filename'] for filenames in app_tables.documents.search()
}

Assembling all those method definitions back together, here's the final code for the whole program we've come up with so far:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.drop_down_1.items = {
      filenames['filename'] for filenames in app_tables.documents.search()
    }

  def text_area_1_change(self, **event_args):
    self.rich_text_1.content = self.text_area_1.text

  def drop_down_1_change(self, **event_args):
    for row in app_tables.documents.search():
      if row['filename'] == self.drop_down_1.selected_value:
        self.text_area_1.text = row['markdown']
        self.text_box_1.text = row['filename']

  def text_box_1_pressed_enter(self, **event_args):
    file = self.text_box_1.text
    found_file = app_tables.documents.get(filename=file)
    if found_file:
      found_file['markdown'] = self.text_area_1.text
    else:    
      app_tables.documents.add_row(
        filename=file, 
        markdown=self.text_area_1.text
      )
    self.drop_down_1.items = {
      filenames['filename'] for filenames in app_tables.documents.search()
    }

Run the app, and it works exactly to our original specifications. After testing the app a bit, I realize that I don't like that there's no button for the user to click, to save the markdown code to a filename in the database - the UI currently requires pressing the Enter key after editing the filename. As a convenience for the user, I'll add a button widget to the design and set its click event to run the text_box_1_pressed_enter method (you've been through this routine in previous tutorial examples). I'm happy with the results:







That didn't require much revision! You can run the finished app at http://markdown.anvil.app

26.2 FTP File Manager

I want to create a little utility app that allows me to download and upload files to several hosted web server accounts. If you're not familiar with how this works, Google 'FTP server'. I'll use the description/outline/pseudo-code process again, to work out how to build it.

  • Description: The app should store the FTP login information for each of the web servers I want to download from and upload to. It should allow me to pick a pre-defined server to log into, perform the login routine, and provide a list of files in a chosen folder on the server, and allow me to download any selected file. It should allow me to select files from my local hard drive, and then perform a file transfer into a selected folder on the server. So, how might a design interface that enables these processes look? ... Well, there needs to be a list of server accounts displayed on screen, to choose from. There also needs to be some list display of files which exist on the chosen server, with a way to select files from that list to download. There also needs to be a button in the UI design that allows us to choose files on the local hard drive, to be uploaded.
  • Design Components: You've seen drop_down selector widgets used in several previous apps. We can use a drop_down to allow the user to select a server. You've also seen repeating_panel widgets in a few database app examples. We can use one of them to display names of files on the server, to be downloaded. Finally, you've seen an Anvil file_loader widget used in the image upload app earlier in this tutorial. That can be used to choose files to be uploaded to a server.
  • Now, what needs to happen when the user interacts with each of the widgets? Well, when the program starts, the first thing the user will do is select a server to connect to, from the drop_down list. Since this is just a simple personal utility app, we can start out by hard-coding a few server names in the drop_down items list, and store the login information in a dictionary data structure, right in the program code. When the user selects a server from the drop_down list, the 'drop_down_1_change' method will need to execute the code that connects with the chosen server, and download a list of files on the server. That list can then be displayed in the repeating panel, and when the user clicks a file name, the method which handles that click event should execute the Python code needed to download the file. Finally, the 'file_loader_1_change' method needs to execute the Python code which transfers files to the server.

Ok let's drag and drop the widgets we've described above onto a new Anvil app design:



We can add some pseudo-code at the start of the program, and in the methods for each widget, as described above:

# At the start of the program, define a Python dictionary data 
# structure which stores each of the server labels, and associates each
# label with a list containing URL, username, password, and folder values
# for each account.   

def __init__(self, **properties):
  # Load the keys from the dictionary above into the drop_down_1 widget.

drop_down_1_change():
  # Assign variable labels to the username and password associated 
  #   with the selected server value.
  # Retrieve a list of files on the server, with some Python FTP code 
  #   that uses the above variables.
  # Display each item in that retrieved list as a row in repeating_panel_1.

repeating_panel_text_click():
  # Download the selected file from the server, with some Python FTP code 
  #   that uses the above variables, concatenated with the chosen FTP URL.

file_loader_1_change():
  # Upload the selected file to the server, with some Python FTP code 
  #   that uses the above variables.
  # Execute the drop_down_1_change method, so that the user can see that
  #   the newly uploaded file is in the updated list of files on the server.

To begin writing some real Python code to replace the pseudo-code above, let's start with the server login dictionary definition at the beginning of the program. We'll assign a variable label 'servers' to it, and follow our pseudo-code description:

servers = {
  'account1' : ['myaccounturl1.com', 'username1', 'password1', '/myfolder'],
  'account2' : ['myaccounturl2.com', 'username2', 'password2', '/public_html'],
  'account3' : ['myaccounturl3.com', 'username3', 'password3', '/afolder']
}

In the __init__ method, here's some actual code that collects all the keys from the dictionary above, and assigns those values to the '.items' property of drop_down_1 (where the user can select one of them):

self.drop_down_1.items = [key for key in servers.keys()]

Now let's look at the drop_down_1_change method. The main work here is to write some code that downloads the list of files stored in a selected folder on our FTP server. We can Google 'python ftp file list' to learn how to get file lists from an FTP server with Python. Here's some code, which is a combination of example code at the following pages, from the Google search: https://stackoverflow.com/questions/67520579/uploading-a-files-in-a-folder-to-ftp-using-python-ftplib and https://www.geeksforgeeks.org/how-to-list-all-files-and-directories-in-ftp-server-using-python/ :

import ftplib

FTP_HOST = "host"
FTP_USER = "user"
FTP_PASS = "pass"
FTP_FOLDER = "/public_html"

ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS)
ftp.encoding = "utf-8"
ftp.cwd(FTP_FOLDER)
files = ftp.nlst()
ftp.quit()

Let's refactor that a bit to use the url, username, and password values saved in the 'servers' dictionary (and move the import of ftplib to the begining of the program):

login = servers[self.drop_down_1.selected_value]
FTP_HOST = login[0]
FTP_USER = login[1]
FTP_PASS = login[2]
FTP_FOLDER = login[3]

ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS)
ftp.encoding = "utf-8"
ftp.cwd(FTP_FOLDER)
files = ftp.nlst()
ftp.quit()

Run that in the drop_down_1_change method and ... uh oh, we get an error message saying that that library can't be used in front-end client code. So, let's refactor the necessary parts of the code as a server callable function:

@anvil.server.callable
def list_ftp_files(FTP_HOST, FTP_USER, FTP_PASS, FTP_FOLDER):
  FTP_HOST = FTP_HOST
  FTP_USER = FTP_USER
  FTP_PASS = FTP_PASS

  ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS)
  ftp.encoding = "utf-8"
  ftp.cwd(FTP_FOLDER)
  files = ftp.nlst()
  ftp.quit()
  return files

We can pass the login values from the front end (again, this is a personal app, so we're allowing some critical data to stay in the front end - we can refactor later, if we ever want others to have access to the app):

def drop_down_1_change(self, **event_args):

  login = servers[self.drop_down_1.selected_value]
  FTP_HOST = login[0]
  FTP_USER = login[1]
  FTP_PASS = login[2]
  FTP_FOLDER = login[3]

  files = anvil.server.call(
    'list_ftp_files',
    FTP_HOST,
    FTP_USER,
    FTP_PASS,
    FTP_FOLDER
  )

And to satisfy the last requirement in our pseudo-code for this method, this line updates the list in repeating_panel_1 with the downloaded list of files (add it to the end of the function above):

self.repeating_panel_1.items = files

That works! But... as I run and test the code, there's an annoyance: as part of any FTP folder listing, the 'ftp.nlst' function in the example code will always return './' and '../' included in the folder list results (which refer to the current and parent folders on the server). Let's remove those elements from the directory list. We can use a 'for' loop and an 'if' conditional evaluation to go through the list and remove those items (add this to the drop_down_1_change method):

for i in ['.', '..']:
  files.remove(i)

Well, that's more like it - we get a nice clean list displayed, of only the files in the remote FTP folder.

So now let's move on to our next pseudo-code method definition, the click handler for each template line in the repeating panel. We need some code that will download the clicked file on any row in the repeating_panel (which now displays the files in the remote folder). A google search for 'Python download file FTP' gives us some examples at https://stackoverflow.com/questions/11573817/how-to-download-a-file-via-ftp-with-python-ftplib . After a little experimentation, this one works well:

A = filename

ftp = ftplib.FTP("IP")
ftp.login("USR Name", "Pass")
ftp.cwd("/Dir")

try:
    ftp.retrbinary("RETR " + filename ,open(A, 'wb').write)
except:
    print "Error"

That can use some variables which are similar to the code in our previous example from Google (in the ftp.login and ftp.cwd functions). Let's refactor that into another Anvil server function:

@anvil.server.callable
def download_ftp_file(FTP_HOST, FTP_USER, FTP_PASS, FTP_FOLDER, FTP_FILENAME):
  ftp = ftplib.FTP(FTP_HOST)
  ftp.login(FTP_USER, FTP_PASS)
  ftp.cwd(FTP_FOLDER)
  try:
      ftp.retrbinary("RETR " + FTP_FILENAME ,open(FTP_FILENAME, 'wb').write)
  except:
      print ("Error downloading")

And call it from the link_1_click method, which is executed when the user clicks a link on a row of the repeating_panel_1 file list:

def link_1_click(self, **event_args):
login = servers[get_open_form().drop_down_1.selected_value]
FTP_HOST = login[0]
FTP_USER = login[1]
FTP_PASS = login[2]
FTP_FOLDER = login[3]
FTP_FILENAME = self.item
file = anvil.server.call(
  'download_ftp_file',
  FTP_HOST,
  FTP_USER,
  FTP_PASS,
  FTP_FOLDER,
  FTP_FILENAME
)

Woohoo, that works to download the selected file onto the hard drive of the Anvil server, but how do we get the file downloaded from there to our local hard drive? Well, Google to the rescue! A quick search for 'anvil.works download file' brings us to https://anvil.works/docs/media , where the last example explains:

Read from files to Media objects using this function:

import anvil.media

anvil.media.from_file(file_name, [content_type], [name])

We can use that last line in our download_ftp_file server function like this:

contents = anvil.media.from_file(FTP_FILENAME)
return contents

A bit more reading of the documentation at https://anvil.works/docs/media tells us that the function belows is what we need in the front end form code to download the selected file (add this to the end of the link_1_click method):

anvil.media.download(file)

Run it and... oh no, we get an error:

NameError: name 'servers' is not defined
at Form1.ItemTemplate1, line 12

Ok, what's happening here is that we've defined the 'servers' variable, which contains all the login credential values for our server accounts, at the beginning of the Form1 code. Well, the code we're writing here is on the 'Form1.ItemTemplate1' form, which is a different code module, so the 'servers' variable is undefined. There's an easy fix for that. We'll create a new code module to hold the 'servers' variable data, and import that module at the beginning of both the Form1 and Form.ItemTemplate1 code:



To include that code in each form where we need the values in the 'servers' variable, be sure to import that module at the beginning of Form1 and Form.ItemTemplate1:

from ..Module1 import servers

And ... Boom - it works! We now have a fully functional version of some code which downloads a file list from a remote FTP folder, displays the list in a repeating panel, and then downloads the selected file from that list to the user's local hard drive.

All we need to do now is convert the file_loader_1_change method pseudo-code to Python. Google 'Python ftp upload' and the first link at https://stackoverflow.com/questions/17438096/ftp-upload-files-python gives us this example code:

import ftplib
import os
filename = "MyFile.py"
ftp = ftplib.FTP("xx.xx.xx.xx")
ftp.login("UID", "PSW")
ftp.cwd("/Unix/Folder/where/I/want/to/put/file")
os.chdir(r"\\windows\folder\which\has\file")
myfile = open(filename, 'r')
ftp.storlines('STOR ' + filename, myfile)
myfile.close()

There are again some similaries with the login variable values we've used in previous code examples. Let's refactor the code above into a final Anvil server function. We learned earlier in the tutorial how to use the file_loader widget, to get a file uploaded from the user's local hard drive, to the Anvil server. The documentation at https://anvil.works/docs/media/files_on_disk explains how to save the uploaded file into a temporary file on disk at the Anvil server, which is needed for the ftp.storlines() function above:

with anvil.media.TempFile(media_object) as file_name:

And here's the refactored code above as an Anvil server function (I incorporated the try/except code from the previous server function definition, to handle any errors that may occur):

@anvil.server.callable
def upload_ftp_file(FTP_HOST, FTP_USER, FTP_PASS, FTP_FOLDER, FTP_FILENAME, file):
  ftp = ftplib.FTP(FTP_HOST)
  ftp.login(FTP_USER, FTP_PASS)
  ftp.cwd(FTP_FOLDER)
  with anvil.media.TempFile(file) as file_name:
    myfile = open(file_name, 'rb')
    try:
      ftp.storbinary("STOR " + FTP_FILENAME,myfile)
    except:
      print('Error uploading')
    myfile.close()

Tada, we've got a working method which uploads files from the user hard drive to the FTP server! Here's the final code for all the server functions, re-assembled from the work above:

import anvil.server
import ftplib
import os
import anvil.media

@anvil.server.callable
def list_ftp_files(FTP_HOST, FTP_USER, FTP_PASS, FTP_FOLDER):
  FTP_HOST = FTP_HOST
  FTP_USER = FTP_USER
  FTP_PASS = FTP_PASS

  ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS)
  ftp.encoding = "utf-8"
  ftp.cwd(FTP_FOLDER)
  files = ftp.nlst()
  ftp.quit()
  return files

@anvil.server.callable
def download_ftp_file(FTP_HOST, FTP_USER, FTP_PASS, FTP_FOLDER, FTP_FILENAME):
  ftp = ftplib.FTP(FTP_HOST)
  ftp.login(FTP_USER, FTP_PASS)
  ftp.cwd(FTP_FOLDER)
  try:
      ftp.retrbinary("RETR " + FTP_FILENAME ,open(FTP_FILENAME, 'wb').write)
  except:
      print ("Error downloading")
  contents = anvil.media.from_file(FTP_FILENAME)
  return contents

@anvil.server.callable
def upload_ftp_file(FTP_HOST, FTP_USER, FTP_PASS, FTP_FOLDER, FTP_FILENAME, file):
  ftp = ftplib.FTP(FTP_HOST)
  ftp.login(FTP_USER, FTP_PASS)
  ftp.cwd(FTP_FOLDER)
  with anvil.media.TempFile(file) as file_name:
    myfile = open(file_name, 'rb')
    try:
      ftp.storbinary("STOR " + FTP_FILENAME,myfile)
    except:
      print('Error uploading')
    myfile.close()

Here's all the code we've added to the methods of widgets on Form1, to call the server functions above:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.server
from ..Module1 import servers

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.drop_down_1.items = [key for key in servers.keys()]

  def drop_down_1_change(self, **event_args):

    login = servers[self.drop_down_1.selected_value]
    FTP_HOST = login[0]
    FTP_USER = login[1]
    FTP_PASS = login[2]
    FTP_FOLDER = login[3]

    files = anvil.server.call(
      'list_ftp_files',
      FTP_HOST,
      FTP_USER,
      FTP_PASS,
      FTP_FOLDER
    )
    for i in ['.', '..']:
      files.remove(i)
    self.repeating_panel_1.items = files

  def file_loader_1_change(self, file, **event_args):

    login = servers[self.drop_down_1.selected_value]
    FTP_HOST = login[0]
    FTP_USER = login[1]
    FTP_PASS = login[2]
    FTP_FOLDER = login[3]
    FTP_FILENAME = file.name

    file = anvil.server.call(
      'upload_ftp_file',
      FTP_HOST,
      FTP_USER,
      FTP_PASS,
      FTP_FOLDER,
      FTP_FILENAME,
      file
    )

    self.drop_down_1_change()

And here's the method for the Form1.ItemTemplate1 click handler, which runs the download function for the selected file:

from ._anvil_designer import ItemTemplate1Template
from anvil import *
import anvil.server
from ...Module1 import servers

class ItemTemplate1(ItemTemplate1Template):
  def __init__(self, **properties):
    self.init_components(**properties)

  def link_1_click(self, **event_args):

    login = servers[get_open_form().drop_down_1.selected_value]
    FTP_HOST = login[0]
    FTP_USER = login[1]
    FTP_PASS = login[2]
    FTP_FOLDER = login[3]
    FTP_FILENAME = self.item

    file = anvil.server.call(
      'download_ftp_file',
      FTP_HOST,
      FTP_USER,
      FTP_PASS,
      FTP_FOLDER,
      FTP_FILENAME
    )

    anvil.media.download(file)

You'll need to edit the Module1 code with actual login credential values for your own servers:

servers = {
  'account1' : ['myaccounturl1.com', 'username1', 'password1', '/myfolder'],
  'account2' : ['myaccounturl2.com', 'username2', 'password2', '/public_html'],
  'account3' : ['myaccounturl3.com', 'username3', 'password3', '/afolder']
}

We've done a lot of refactoring and testing along the way, so this app is now fully working according to our original required specifications:



Because this application provides access to files on a private server, with private login credentials, you'll need to run the app yourself to see it work.

26.3 Shift Clock

I want to create a time clock app to track the hourly shifts worked by employees. I'll use the description/outline/pseudo-code process again, to work out how to build it.

  • Description: The app should provide a way for workers to clock in and clock out, and there needs to be a reporting mechanism which calculates and displays the number of hours worked by any employee between any 2 dates and times. To verify that each employee is actually clocking in and out their own hours, I want the app to take a photo of each employee when they log in/out. So, how might a design that enables these processes look? ... Well, there needs to be a list of selectable employees, and a way for employees to stop and start the clock, and to take a photo. There also needs to be a password protected admin page which enables the user to select an employee, choose dates, and view the reported total hours calculation. There should also be a way for admins to add new employees. Internally, there need to be ways to store all the login/logout inputs and photo data, and logic to perform the worked hours calculations.
  • Design Components: We'll create 2 separate form designs - 1 for the employee login screen, and 1 for the admin features. We'll use navigation links to switch between the 2 forms. When the user clicks the link to switch to the admin screen, we can use an pop-up alert requestor for password entry. On the employee form, we can use a drop_down widget to enable employees to select their name, a file_loader widget to snap and upload a photo, an image widget to display the captured photo, and 2 buttons to start and stop the clock at current login and logout times. On the admin form, we can use a text_box to enable the entry of new employee names. A drop_down can be used to select the employee for whom a report will be run, and 2 date_picker widgets can be used to select the start and stop dates. A button can be used to start the report generation routine, and a text area can be used to display the computed results. Database tables can be used to store all the information needed in this app.
  • Now, what needs to happen when the user interacts with each of the widgets? Well, when the program starts, the first thing an employee will do is select their name from the drop_down list, snap a photo, and click either the login or logout button. Each of those fields of data should be saved to a row of the database. Since this app handles info that we don't want the user to be able to alter, we should only interact with the database in server functions. When the user clicks the admin link, the password entered into the requestor should be validated in a server function, and then the admin form should be displayed. If the admin enters a new employee into the text_box, that name should be added to a table of employees in the database. When the admin selects an employee from the drop_down, start and stop dates from the date_pickers, and then clicks the button, some server code should search the database for all start and start dates for the selected employee, and use a 'for' loop to go through that list, tallying the time difference between each successive login and logout time. The returned calculation should be displayed in the text_area widget.

Ok let's drag and drop the widgets we've described above onto a new Anvil app design:





We can add some pseudo-code at the start of the program, and in the methods for each widget, as described above:

def __init__(self, **properties):
  # Search the employee database table for all the employee names
  #   and set the '.items' property of drop_down_1 to that list.

def link_1_click(self, **event_args):
  # Use an alert pop-up to request the password.  Run a function on
  # the server to validate the saved password value (on the server because
  # we don't want that password to be stored anywhere in the client's
  # web browser where they could potentially find it in the code).
  # If the entered password matches, open the Admin form.  If not,
  # alert an 'Incorrect password' message.

def file_loader_1_change(self, file, **event_args):
  # When the user clicks the file_loader option on their mobile
  # device, one of the options that automatically comes up is to use
  # the camera to take a photo.  We'll display the photo in the
  # image_1 widget.

def button_1_click(self, **event_args):
  # Pass the name currently selected in drop_down_1 to a server
  # function, to store the name and current time in the login column
  # of a database row (on the server because we don't want employees
  # to have access to adjust any saved clock values).  Alert the user
  # when the server function has completed.

def button_2_click(self, **event_args):
  # Exactly the same as the button_1_click method, except store the
  # current time in the logout column of a database row.

We need to create some database tables to handle all the data in the functions above. Let's create one table called 'employees', with one column of text values, to contain the list of all employee names. It's fine to set the form permissions of this table to 'can search table'. This doesn't give the user any access to make changes to the database from front end code, and we are making all the names in this table visible to the user anyway, in a drop_down list, so there's no reason not to allow it to be searchable:



We'll need another table with 'name', 'login', 'logout', and 'photo' columns. We'll call that table 'shifts'. Be sure to set the form permissions of this table to 'no access':



Now let's start hacking at converting all the pseudo-code directions above to Python code.

For the __init__ method, you've seen this sort of list comprehension many times already, to collect a list of all the values in the employee name column, and set it to the '.items' property of the drop_down list:

self.drop_down_1.items = {
  names['name'] for names in app_tables.employees.search()
}

For the link_1_click method, you've seen this sort of alert requestor before, which we can use to get a password from the user:

t = TextBox(placeholder='admin password')
alert(content=t, title='Enter password: ')

We'll use a server function which we'll call 'password', to test if a hard-coded password string matches what the user has typed. If so we'll open the admin form. If not, we'll alert the user that the password is incorrect:

if True == anvil.server.call('password', t.text):
  open_form('Admin')
else:
  alert('Incorrect password')

The 'password()' server function definition is a single line, simply returning True is the password entered by the user matches a hard-coded string. Remember, the only reason we're using a server function here to hold that password, is so that experienced users won't be able to find the password stored in the code running in their browser:

@anvil.server.callable
def password(passwrd):
  if passwrd = 'password': return True

Next, we'll write the file_loader_1_change method. You seen this before - just set the '.source' property of image_1 widget to the automatically created 'file' argument of the file_loader_1_change method:

self.image_1.source = file

Now we'll write the button_1_click method: Here's how we pass the name selected from drop_down_1 widget, and the image data from the file_loader_1 widget, to the server function which we'll call 'login':

anvil.server.call(
  'login', 
  self.drop_down_1.selected_value,
  self.image_1.source
)

And you know how to alert the user when that function has completed:

alert('Logged in!')

The server 'login' function just needs to take the 'name' and 'image' arguments (the values which have been passed, from the selected name in the front end drop_down_1 widget and image_1 widget), and add them to the 'name' and 'image' columns of a new row in the 'shifts' table, while also adding the current date and time to the 'login' column:

@anvil.server.callable
def login(name, image):
  app_tables.shifts.add_row(
    name=name,
    login=datetime.now(),
    image=image
  )

Because we used the now() function from the datetime library, we need to import that library at the beginning of the server code module:

from datetime import datetime

And all the same code can be used for the button_2_click method (the logout button). Just change the server function name to 'logout':

anvil.server.call(
  'logout', 
  self.drop_down_1.selected_value,
  self.image_1.source
)

And be sure to save the current time to the 'logout' column of the 'shifts' table (instead of the 'login' column, as in the login function above):

@anvil.server.callable
def logout(name, image):
  app_tables.shifts.add_row(
    name=name, 
    logout=datetime.now(),
    image=image
)

Assemble all those methods, and we have all the code needed to bring Form1 to life:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.drop_down_1.items = {
      names['name'] for names in app_tables.employees.search()
    }

  def link_1_click(self, **event_args):
    t = TextBox(placeholder='admin password')
    alert(content=t, title='Enter password: ')
    if True == anvil.server.call('password', t.text):
      open_form('Admin')
    else:
      alert('Incorrect password')

  def file_loader_1_change(self, file, **event_args):
    self.image_1.source = file

  def button_1_click(self, **event_args):
    anvil.server.call(
      'login', 
      self.drop_down_1.selected_value,
      self.image_1.source
    )
    alert('Logged in!')

  def button_2_click(self, **event_args):
    anvil.server.call(
      'logout', 
      self.drop_down_1.selected_value,
      self.image_1.source
    )
    alert('Logged out!')

Now let's write the pseudo-code for widget methods on the admin form:

def __init__(self, **properties):
  # We'll use the exact same code as on Form1 to fill the '.items' 
  # property of self.drop_down_1 to the list of names in the 'employees'
  # table.

def text_box_1_pressed_enter(self, **event_args):
  # We'll create a server function to add a new name to the 'employee'
  # database table.  We'll name that function 'add_employee' and pass the
  # text in text_box_1 as the argument.  After the name has been added to
  # the database, refresh the items in drop_down_1.  We can use the exact
  # same code as in the __init__ function to do this.  Then alert the
  # user that the name has been added, and clear the text in text_box_1.

@anvil.server.callable
def add_employee(name):
  # Create a new row in the 'name' column of the 'employee' table in the
  # database, using the 'name' value passed as the argument to this 
  # function.

def link_1_click(self, **event_args):
  # Open Form1 1 to navigate back to the Home screen.  No password is 
  # required to use this page.

def button_1_click(self, **event_args):
  # We'll create a server function called 'search', to perform the
  # calculation of total hours worked by the selected hour.  We'll pass
  # this function the name selected from drop_down_1, the start date
  # selected from date_picker_1 and the end date selected from 
  # date_picker_2.  We'll assign the result of the server calculation
  # to the variable 'total', then set the '.text' property of
  # text_area_1 to that total value.

@anvil.server.callable
def search(name, login, logout):
  # This function on the server will accept the passed 'name', 'login',
  # and 'logout' values as arguments to the function definition (those 
  # values passed to the function by the front-end button_1_click 
  # method above).  We'll search the 'shifts' database table for any
  # row containing the passed 'name' value, and assign the variable
  # 'shifts' to the resulting list.  To start a tally of all the
  # resulting computations we want to perform, we'll create variables
  # to hold the 'total_hours', 'startdate', 'logins', and 'logouts'
  # values we'll work with along the way (setting the numbers to zero,
  # and the lists to empty, so that we can add to them as we tally
  # values from each row in the database).  Use a 'for' loop to go
  # through the 'shifts' list, and if any of the entries in the list
  # has a value in the 'login' column, compare that datetime value to
  # the passed 'login' value (the start date chosen by the user for
  # the report).  If the entry is greater than or equal to the chosen
  # 'login' start date value, then add the entry to the 'logins' list,
  # and set the 'startdate' value to the entry value.  We'll use this
  # variable to keep track of the most recent row which has been
  # determined to be a start value.  If the row contains a 'logout'
  # value, and that logout value is less than or equal to the end date
  # chosen by the user for the report, then we'll append that entry to
  # the 'logouts' list.  If our 'total_hours' tally value is currently
  # zero, then we'll set the 'total_hours' tally to the value of
  # (current entry datetime, minus the startdate value).  Otherwise, if 
  # the 'total_hours' tally has already been initiated in a previous
  # row of the loop evaluation, we'll add the (current row's datetime,
  # minus the startdate) calculation to the 'total_hours' tally.  When
  # the tally of each of these daily total calculations has been
  # completed, we'll return the 'total_hours' value, and the 'login'
  # and 'logout' lists to the front end method.

Now let's start writing Python code for the __init__ function. That's easy - just copy the code we've described from Form1:

self.drop_down_1.items = {
  names['name'] for names in app_tables.employees.search()
}

For the text_box_1 method, call the 'add_employee' function, with the text in text_box_1 as the argument:

anvil.server.call(
  'add_employee', 
  self.text_box_1.text
)

Copy the code again from above, as described in the pseudo-code:

self.drop_down_1.items = {
  names['name'] for names in app_tables.employees.search()
}

Alert the user and clear text_box_1:

alert('Added!')
self.text_box_1.text = ''

The link_1_click definition is just 1 line, to open Form1:

open_form('Form1')

The button_1_click definition calls the 'search' server function (with selected employee and date values passed as arguments), and assigns the returned results to the variable 'total'. The '.text' property of text_area_1 is in turn set to that 'total' value:

total = anvil.server.call(
  'search',
  self.drop_down_1.selected_value,
  self.date_picker_1.date,
  self.date_picker_2.date
)
self.text_area_1.text = total

The 'search' function on the server accepts the argument values sent by the front-end method above:

@anvil.server.callable
def search(name, login, logout):

You've seen how to perform the search described in the pseudo-code, to collect rows from the 'shifts' table which match the chosen employee name:

shifts = app_tables.shifts.search(
  name=name
)

Now we can create the variables we described, all initiated to zeros and empty lists:

total_hours = 0
startdate = 0
logins = []
logouts = []

The 'for' loop we described will go through each row of the 'shifts' list we created above:

for entry in shifts:

If the 'logout' column of any row (which we called 'entry' above) is empty, then we know that row contains a 'login' value:

if entry['logout'] is None:

If the value in the 'login' column of the current row is less than or equal to the 'login' value chosen by the user to begin the report:

if entry['login'] >= login:

then append the 'login' column value of the current row to the 'logins' list, and set the 'startdate' variable to that value:

logins.append(entry['login'])
startdate = entry['login']

Otherwise, if the value in the 'logout' column of the current row is less than or equal to the 'logout' value chosen by the user to end the report:

else:
  if entry['logout'] <= logout:

then append the 'logout' column value of the current row to the 'logouts' list:

logouts.append(entry['logout'])

if the 'total_hours' variable is currently set to zero, then set that value to the result of the computation (logout value in this row - startdate):

if total_hours == 0:
  total_hours = (entry['logout'] - startdate)

Otherwise, add the computation (logout value in this row - startdate) to the current 'total_hours' value:

else:
  total_hours += (entry['logout'] - startdate)

Finally, return the 'total_hours', 'logins', and 'logout' tally values to the front end:

return [str(total_hours), logins, logouts]

It should be pointed out that nearly all the Python code we've written in this example is actually shorter, more concise, and perhaps even more readable than the pseudo-code descriptions we've created using natural language. It shouldn't be too surprising that's the case, because the Python language was designed to be readable in this way, specifically to handle the sorts of evaluations, calculations, and routines which you'll encounter when structuring these sorts of thoughts in code! You'll find that as your Python skills improve, and you become fluent, it will likely be easier to simply write most Python code without needing any pseudo-code thoughts. That goal of being able to 'think in code' is what you're working towards with these sorts of case studies. Eventually pseudo-code may simply be useful when you're writing complex functions, and you need to outline each piece, so you can keep a clear perspective about the broader scope of some code's functionality, without getting bogged down in the details of each piece. As your fluency improves, thinking directly in Python code will become even more natural and effective for defining thoughts about how to structure methods and functions.

Assemble all the method definitions above, and we have all the code needed to get the admin form functioning:

from ._anvil_designer import AdminTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables

class Admin(AdminTemplate):
  def __init__(self, **properties):
    self.init_components(**properties)
    self.drop_down_1.items = {
      names['name'] for names in app_tables.employees.search()
    }

  def text_box_1_pressed_enter(self, **event_args):
    anvil.server.call(
      'add_employee', 
      self.text_box_1.text
    )
    self.drop_down_1.items = {
      names['name'] for names in app_tables.employees.search()
    }
    alert('Added!')
    self.text_box_1.text = ''

  def link_1_click(self, **event_args):
    open_form('Form1')

  def button_1_click(self, **event_args):
    total = anvil.server.call(
      'search',
      self.drop_down_1.selected_value,
      self.date_picker_1.date,
      self.date_picker_2.date
    )
    self.text_area_1.text = total

@anvil.server.callable
def search(name, login, logout):
  shifts = app_tables.shifts.search(
    name=name
    # tables.order_by("login", ascending=True)
  )
  total_hours = 0
  startdate = 0
  logins = []
  logouts = []
  for entry in shifts:
    if entry['logout'] is None:
      if entry['login'] >= login:
        logins.append(entry['login'])
        startdate = entry['login']
    else:
      if entry['logout'] <= logout:
        logouts.append(entry['logout'])
        if total_hours == 0:
          total_hours = (entry['logout'] - startdate)
        else:
          total_hours += (entry['logout'] - startdate)
  return [str(total_hours), logins, logouts]

And here are all the server functions we've created so far in the app:

import anvil.secrets
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server
from datetime import datetime

@anvil.server.callable
def login(name, image):
  app_tables.shifts.add_row(
    name=name,
    login=datetime.now(),
    image=image
  )

@anvil.server.callable
def logout(name, image):
  app_tables.shifts.add_row(
    name=name, 
    logout=datetime.now(),
    image=image
)

@anvil.server.callable
def password(passwrd):
  if passwrd = 'password': return True

@anvil.server.callable
def add_employee(name):
  app_tables.employees.add_row(
    name=name
  )

@anvil.server.callable
def search(name, login, logout):
  shifts = app_tables.shifts.search(
    name=name
    # tables.order_by("login", ascending=True)
  )
  total_hours = 0
  startdate = 0
  logins = []
  logouts = []
  for entry in shifts:
    if entry['logout'] is None:
      if entry['login'] >= login:
        logins.append(entry['login'])
        startdate = entry['login']
    else:
      if entry['logout'] <= logout:
        logouts.append(entry['logout'])
        if total_hours == 0:
          total_hours = (entry['logout'] - startdate)
        else:
          total_hours += (entry['logout'] - startdate)
  return [str(total_hours), logins, logouts]

Run the app, and you'll see we have a 'minimum viable product' working for the specifications we originally described:





It should be clarified, that along the way to this code, I wrote and tested many variations of bits and pieces that didn't initially work exactly as needed. I started with little parts of method definitions and tested the results of each logical evaluation, to ensure that the values being returned were correct. Sometimes I refactored the code to make more sense, or to add some additional features. There's always a lot of that write/test/debug/edit/repeat loop to go through when checking your working code. Including all that noise in this text would add too much confusion, but it should be emphasized that write/test/debug/edit/repeat is the normal process expected in any software development routine. 'Test driven development' is actually an established approach to writing software, which starts with defining tests of your eventually working code, before you write any code. That approach is outside the scope of this tutorial, but worth looking into, if you're interested in anything more than basic development practices. The level we're at right now is to simply focus on breaking down the problems in any program, using some sort of repeatable routine that helps outline code structures and thought processes needed to flesh out the specifications of a vaguely defined app idea, and progress towards writing working code. In the software development industry, there are many established practices which help teams of developers work with clients to establish clear app specifications, and to go through the process of breaking down the problem into manageable chunks, and then write/test/debug/edit/repeat, until a large application is completed - assembling smaller pieces of code into larger structures.

For now, focus on learning this basic process of creating natural language descriptions, outlining user interface widgets and/or server functions, writing some pseudo-code logic in methods and functions, and then converting the pseudo-code logic into actual Python/Anvil code. That pseudo-code step may eventually progress into simply writing simple notes about the broad scope of functionality needed in any large chunk of code. Just break everything down into logical steps and test small pieces of code until they work. Use Google whenever you get stuck, and you'll find answers. That's one of the best things about using Python - there's just so much code readily available online to help you create solutions to your problems. You'll likely spend more time refactoring already working code, than having to write everything from scratch completely, once you get used to the routine.

There is a lot more we can do with this app. Can you imagine how to approach adding user management code which requires each employee to log in with an email? Can you imagine how to approach checking that date and employee values are properly selected before running the report (so that, for example, the dates aren't entered in reverse order)? Can you imagine how to add a full report option which automatically runs reports for every employee for the past week (that's going to require more loops)? You've got enough of the hard work accomplished here to begin adding all those features and more. Give it a shot! Remember, you can save working versions of the app at any point, so you don't need to be worried about breaking existing code.

You can see the current version of this app example running at https://shifts.anvil.app

26.4 Poll Builder

I want to create an app which polls users in an audience for answers to questions, and then shows me a graphic chart of poll results for each question. The goal of creating this app is a bit different than that of others so far. What I want to do is create some generally reusable code which will simplify the process of displaying multiple questions on screen, so that I don't have to go through the time-consuming routine of manually building an interface with drag-and-drop, for every new set of questions in every new poll I generate.

I'll follow a different process to work through making this app, by mostly taking existing code from the Anvil docs, Google result, and previous tutorial examples, and experimenting/refactoring with existing little bits of code structure, until I get a working solution. Let's start by explaining better what I'm trying to accomplish:

I could potentially build an interface for polls by dragging and dropping labels onto a form, to display questions, and then putting a selectable list of answers to questions in drop_down lists. That would work fine, but I want a display that looks more like a traditional poll, written on physical paper - something like a list of questions with all the answers displayed in one large view. I'm imagining a piece of paper that has the list of answers written out and selectable, by circling or checking a single one with a pencil.

Looking at the Anvil toolbox, radio buttons provide a nice analogue to this sort of layout:



The only problem is, I'm imagining having large lists of answers to some questions that look like something like this:

questions = [
  'Colors', [
    'White', 'Yellow', 'Blue', 'Red', 'Green', 'Black', 'Brown', 'Azure',
    'Ivory', 'Teal', 'Silver', 'Purple', 'Navy blue', 'Pea green', 'Gray',
    'Orange', 'Maroon', 'Charcoal', 'Aquamarine', 'Coral', 'Fuchsia', 
    'Wheat', 'Lime', 'Crimson', 'Khaki', 'Hot pink', 'Magenta', 'Olden', 
    'Plum', 'Olive', 'Cyan'
  ],
  'Animals', [
    'dog', 'cat', 'lion', 'bear', 'monkey', 'turtle', 'mouse', 'hawk', 
    'dolphin', 'fly', 'snake', 'deer', 'aardvark', 'scorpion', 'chipmunk',
    'squirrel', 'horse', 'rat', 'pigeon', 'lizard', 'elephant', 'pig',
    'guinee pig', 'swan', 'duck', 'goose', 'chicken', 'turkey', 'cow'
  ],
  'Cities', [
    'Albany', 'Amsterdam', 'Auburn', 'Babylon', 'Batavia', 'Beacon', 
    'Bedford', 'Binghamton', 'Bronx', 'Brooklyn', 'Buffalo', 'Chautauqua', 
    'Cheektowaga', 'Clinton', 'Cohoes', 'Coney Island', 'Cooperstown', 
    'Corning', 'Cortland', 'Crown Point', 'Dunkirk', 'East Aurora', 
    'East Hampton', 'Eastchester', 'Elmira', 'Flushing', 'Forest Hills', 
    'Fredonia', 'Garden City', 'Geneva', 'Glens Falls', 'Gloversville', 
    'Great Neck', 'Hammondsport', 'Harlem', 'Hempstead', 'Herkimer', 
    'Hudson', 'Huntington', 'Hyde Park', 'Ilion', 'Ithaca', 'Jamestown', 
    'Johnstown', 'Kingston', 'Lackawanna', 'Lake Placid', 'Levittown', 
    'Lockport', 'Mamaroneck', 'Manhattan', 'Massena', 'Middletown', 
    'Mineola','Mount Vernon', 'New Paltz', 'New Rochelle', 'New Windsor', 
    'New York City', 'Newburgh', 'Niagara Falls', 'North Hempstead', 
    'Nyack', 'Ogdensburg', 'Olean', 'Oneida', 'Oneonta', 'Ossining', 'Oswego',
    'Oyster Bay', 'Palmyra', 'Peekskill', 'Plattsburgh', 'Port Washington', 
    'Potsdam', 'Poughkeepsie', 'Queens', 'Rensselaer', 'Rochester', 'Rome', 
    'Rotterdam', 'Rye', 'Sag Harbor', 'Saranac Lake', 'Saratoga prings', 
    'Scarsdale', 'Schenectady', 'Seneca Falls', 'Southampton', 
    'Staten Island', 'Stony Brook', 'Stony Point', 'Syracuse', 'Tarrytown', 
    'Ticonderoga', 'Tonawanda', 'Troy', 'Utica', 'Watertown', 'Watervliet', 
    'Watkins Glen', 'West Seneca', 'White Plains', 'Woodstock', 'Yonkers'
  ]
]

Configuring each individual radio button requires dragging and dropping the widget onto a design form, and then setting 'group_name', 'value', and 'text' properties by pointing, clicking, and typing into fields in the visual editor. The time required to set up such a large number of radio buttons for questions on multiple polls is just too prohibitive. So what I'd like to do is come up with some code which will generate radio button layouts from lists such as the one above. You've seen a process similar in concept, described in the calculator example earlier in the tutorial.

I'll start by looking up some documentation about how to create/interact with radio buttons using Anvil code. A Google search for 'anvil.works radio button' brings me to https://anvil.works/docs/client/components/basic#radiobutton, where this example code is provided:

rb1 = RadioButton(text="Select this option")
rb1.group_name = "mygroup"
rb1.value = "A"

rb2 = RadioButton(text="Or select this one", group_name="mygroup", value="B")

# Add these radio buttons to the Form
self.add_component(rb1)
self.add_component(rb2)

rb1.selected = True
print(rb1.get_group_value())  # prints A

rb2.selected = True # This will deselect the other button
print(rb2.get_group_value())  # prints B

Great, that let's me put a single button on screen, set the required properties, and get a selected answer value which has chosen by the user. I'll refactor that code, first determining only the pieces I need:

rb1 = RadioButton(text="Select this option")
rb1.group_name = "mygroup"
rb1.value = "A"
self.add_component(rb1)

And then change the variable labels and string values to suit one of my example questions. This should all go in the form_show event handler - be sure to set this event to run when the form is shown, in the event properties of the form (in the visual designer for Form1 (as we did when drawing canvas graphics)):

def form_show(self, **event_args):
  widget = RadioButton(text='dog')
  widget.groupname = 'animals'
  widget.value = 'dog'
  self.add_component(widget)

Now I can use a loop to add multiple instances of the code above to a form, using variables in place of the hard-coded 'dog' and 'animals' values above:

def form_show(self, **event_args):
  animals = [
    'dog', 'cat', 'lion', 'bear', 'monkey', 'turtle', 'mouse', 'hawk', 
    'dolphin', 'fly', 'snake', 'deer', 'aardvark', 'scorpion', 'chipmunk',
    'squirrel', 'horse', 'rat', 'pigeon', 'lizard', 'elephant', 'pig',
    'guinea pig', 'swan', 'duck', 'goose', 'chicken', 'turkey', 'cow'
  ]
  for animal in animals:
    widget = RadioButton(text=animal)
    widget.groupname = 'animals'
    widget.value = animal
    self.add_component(widget)

That works, but each radio button appears on a new row. Let's look at the calculator example code from earlier in the tutorial for some existing code that places widgets into a row/column layout:

from ._anvil_designer import Form1Template
from anvil import *
class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.txt = TextBox()
    self.add_component(self.txt)
    grd = GridPanel()

    nums = [['1','2', '3','+'], ['4','5', '6', '-'], 
            ['7', '8', '9', '.'], ['0', '*', '/', '=']]
    rows = ['A', 'B', 'C', 'D']
    for (nm, rw) in zip(nums, rows): 
      for n in nm:
        btn = Button(text=n)
        btn.set_event_handler('click', self.click)
        grd.add_component(btn, row=rw, col_xs=4, width_xs=1)

    self.add_component(grd)

  def click(self, **event_args):
    chr = event_args['sender'].text
    self.txt.text += chr

Refactoring the code above to fit my radio button code gives me this:

animals = [
  ['dog', 'cat', 'lion', 'bear'],
  ['monkey', 'turtle', 'mouse', 'hawk'],
  ['dolphin', 'fly', 'snake', 'deer'],
  ['aardvark', 'scorpion', 'chipmunk', 'squirrel']
]
rows = ['A', 'B', 'C', 'D']
grd = GridPanel()
for (animal, rw) in zip(animals, rows): 
  for a in animal:
    widget = RadioButton(text=a)
    widget.groupname = 'animals'
    widget.value = a
    widget.set_event_handler('click', self.click)
    grd.add_component(widget, row=rw, col_xs=4, width_xs=1)
self.add_component(grd)

def click(self, **event_args):
chr = event_args['sender'].text

Ugg, I don't like that. Using the GridPanel code above works, but requires lots of formatting the data definition into multiple lists within lists, so values can be aligned into evenly spaced columns. I think the better layout widget for my purposes here is a FlowPanel, which simply places any number of consecutive widgets next to each other horizontally, on rows that wrap vertically, whenever a row is full (the same way multi-line text wraps in a word processor). So I experimented again, refactoring the code above to come up with the most basic possible loop, to add multiple instances of my required radio button code to a FlowPanel, and this is what I ended up with:

def form_show(self, **event_args):
  flw = FlowPanel()
  animals = [
    'dog', 'cat', 'lion', 'bear', 'monkey', 'turtle', 'mouse', 'hawk', 
    'dolphin', 'fly', 'snake', 'deer', 'aardvark', 'scorpion', 'chipmunk',
    'squirrel', 'horse', 'rat', 'pigeon', 'lizard', 'elephant', 'pig',
    'guinea pig', 'swan', 'duck', 'goose', 'chicken', 'turkey', 'cow'
  ]
  for animal in animals:
    widget = RadioButton(text=animal)
    widget.value = animal
    widget.groupname = 'animals'
    widget.add_event_handler('clicked', self.radio_clicked)
    flw.add_component(widget)
  self.add_component(flw)

Now that's much simpler, it doesn't require any reformatting of the list data structure, and actually looks just as I'd hoped, flowing nicely with resized screens:





Ok, so far I've got a nice loop that builds a group of radio buttons from a list of selectable answers to a single poll question (animals). Next up, I want to enclose that single list of animals, in a larger list of questions and their related answers. That's going to require an additional loop. I want the list to contain pairs of poll question strings, followed by the related potential answers that the user can choose as a response to the question. There's a perfect code example showing how to deal with consecutive pairs of values in a list at https://stackoverflow.com/questions/5389507/iterating-over-every-two-elements-in-a-list:

>>> l = [1,2,3,4,5,6]

>>> zip(l,l[1:])
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

>>> zip(l,l[1:])[::2]
[(1, 2), (3, 4), (5, 6)]

>>> [a+b for a,b in zip(l,l[1:])[::2]]
[3, 7, 11]

>>> ["%d + %d = %d" % (a,b,a+b) for a,b in zip(l,l[1:])[::2]]
['1 + 2 = 3', '3 + 4 = 7', '5 + 6 = 11']

I refactored that example, and used a 'for' loop to add Label and Spacer widgets to separate poll questions in the layout:

def form_show(self, **event_args):
  flw = FlowPanel()
  radios = [
    'Colors', [
      'White', 'Yellow', 'Blue', 'Red', 'Green', 'Black', 'Brown', 'Azure',
      'Ivory', 'Teal', 'Silver', 'Purple', 'Navy blue', 'Pea green', 'Gray',
      'Orange', 'Maroon', 'Charcoal', 'Aquamarine', 'Coral', 'Fuchsia', 
      'Wheat', 'Lime', 'Crimson', 'Khaki', 'Hot pink', 'Magenta', 'Olden', 
      'Plum', 'Olive', 'Cyan'
    ],
    'Animals', [
      'dog', 'cat', 'lion', 'bear', 'monkey', 'turtle', 'mouse', 'hawk', 
      'dolphin', 'fly', 'snake', 'deer', 'aardvark', 'scorpion', 'chipmunk',
      'squirrel', 'horse', 'rat', 'pigeon', 'lizard', 'elephant', 'pig',
      'guinea pig', 'swan', 'duck', 'goose', 'chicken', 'turkey', 'cow'
    ],
    'Cities', [
      'Albany', 'Amsterdam', 'Auburn', 'Babylon', 'Batavia', 'Beacon', 
      'Bedford', 'Binghamton', 'Bronx', 'Brooklyn', 'Buffalo', 'Chautauqua', 
      'Cheektowaga', 'Clinton', 'Cohoes', 'Coney Island', 'Cooperstown', 
      'Corning', 'Cortland', 'Crown Point', 'Dunkirk', 'East Aurora', 
      'East Hampton', 'Eastchester', 'Elmira', 'Flushing', 'Forest Hills', 
      'Fredonia', 'Garden City', 'Geneva', 'Glens Falls', 'Gloversville', 
      'Great Neck', 'Hammondsport', 'Harlem', 'Hempstead', 'Herkimer', 
      'Hudson', 'Huntington', 'Hyde Park', 'Ilion', 'Ithaca', 'Jamestown', 
      'Johnstown', 'Kingston', 'Lackawanna', 'Lake Placid', 'Levittown', 
      'Lockport', 'Mamaroneck', 'Manhattan', 'Massena', 'Middletown', 
      'Mineola', 'Mount Vernon', 'New Paltz', 'New Rochelle', 'New Windsor', 
      'New York City', 'Newburgh', 'Niagara Falls', 'North Hempstead', 
      'Nyack', 'Ogdensburg', 'Olean', 'Oneida', 'Oneonta', 'Ossining', 'Oswego',
      'Oyster Bay', 'Palmyra', 'Peekskill', 'Plattsburgh', 'Port Washington', 
      'Potsdam', 'Poughkeepsie', 'Queens', 'Rensselaer', 'Rochester', 'Rome', 
      'Rotterdam', 'Rye', 'Sag Harbor', 'Saranac Lake', 'Saratoga Springs', 
      'Scarsdale', 'Schenectady', 'Seneca Falls', 'Southampton', 
      'Staten Island', 'Stony Brook', 'Stony Point', 'Syracuse', 'Tarrytown', 
      'Ticonderoga', 'Tonawanda', 'Troy', 'Utica', 'Watertown', 'Watervliet', 
      'Watkins Glen', 'West Seneca', 'White Plains', 'Woodstock', 'Yonkers'
    ]
  ]
  for key, values in list(zip(radios, radios[1:]))[::2]:
    flw.add_component(Label(text=(key + ":"), font_size=25))
    flw.add_component(Spacer(height=1, width=3000))
    for value in values:
      widget = RadioButton(text=value)
      widget.value = value
      widget.groupname = key
      widget.add_event_handler('clicked', self.radio_clicked)
      flw.add_component(widget)
    flw.add_component(Spacer(height=1, width=3000))
  self.add_component(flw)

Notice that I added an event handler to each of the radio buttons, which will execute the 'radio_clicked' method, whenever the user clicks a radio button. I want that method to store the currently selected answer (the chosen radio button value) and its related question string to a list. In order to do that, a dictionary seems to be the best data structure (it holds pairs of related values). I'll create a new empty list at the very beginning of the program (right after the imports), to hold this info. I'll label that dictionary 'selected':

selected = {}

I'll use the '.update' method that comes built into every Python dictionary, to either add or replace the current question/answer choices stored in the dictionary. An example of how to do this can be found at https://www.geeksforgeeks.org/python-dictionary-update-method/ (thanks to a Google search for 'python update dictionary'):

# Python program to show working
# of update() method in Dictionary

# Dictionary with three items
Dictionary1 = {'A': 'Geeks', 'B': 'For', }
Dictionary2 = {'B': 'Geeks'}

# Dictionary before Updation
print("Original Dictionary:")
print(Dictionary1)

# update the value of key 'B'
Dictionary1.update(Dictionary2)
print("Dictionary after updation:")
print(Dictionary1)

We can refactor the way the code above works, and combine it with the event handler method from the calculator example earlier in the tutorial, to get this:

def radio_clicked(self, **event_args):
  chosen = event_args['sender'].get_group_value()
  category = event_args['sender'].groupname
  selected.update({category:chosen})

We're also going need to add a button to the layout design, to handle when the user wants to submit their answers to the poll. The following code is similar to the how we've added other widgets to the layout. It does the job, and should be added to the form_show method, right after the loop which displays all the questions and radio buttons:

self.add_component(Spacer(height=1, width=3000))
btn = Button(text='SUBMIT')
btn.set_event_handler('click', self.submit)
self.add_component(btn)

We'll also need an event handler method to execute, whenever the user clicks the button above. For now, we'll just print the contents of the 'selected' dictionary (all the pairs of questions and associated answers which the user has selected by clicking radio buttons):

def submit(self, **event_args):
  print(selected)

Re-assemble all the pieces so far, and this is the code for the app, as it stands now:

from ._anvil_designer import Form1Template
from anvil import *

selected = {}

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def form_show(self, **event_args):
    flw = FlowPanel()
    radios = [
      'Colors', [
        'White', 'Yellow', 'Blue', 'Red', 'Green', 'Black', 'Brown', 'Azure',
        'Ivory', 'Teal', 'Silver', 'Purple', 'Navy blue', 'Pea green', 'Gray',
        'Orange', 'Maroon', 'Charcoal', 'Aquamarine', 'Coral', 'Fuchsia', 
        'Wheat', 'Lime', 'Crimson', 'Khaki', 'Hot pink', 'Magenta', 'Olden', 
        'Plum', 'Olive', 'Cyan'
      ],
      'Animals', [
        'dog', 'cat', 'lion', 'bear', 'monkey', 'turtle', 'mouse', 'hawk', 
        'dolphin', 'fly', 'snake', 'deer', 'aardvark', 'scorpion', 'chipmunk',
        'squirrel', 'horse', 'rat', 'pigeon', 'lizard', 'elephant', 'pig',
        'guinea pig', 'swan', 'duck', 'goose', 'chicken', 'turkey', 'cow'
      ],
      'Cities', [
        'Albany', 'Amsterdam', 'Auburn', 'Babylon', 'Batavia', 'Beacon', 
        'Bedford', 'Binghamton', 'Bronx', 'Brooklyn', 'Buffalo', 'Chautauqua', 
        'Cheektowaga', 'Clinton', 'Cohoes', 'Coney Island', 'Cooperstown', 
        'Corning', 'Cortland', 'Crown Point', 'Dunkirk', 'East Aurora', 
        'East Hampton', 'Eastchester', 'Elmira', 'Flushing', 'Forest Hills', 
        'Fredonia', 'Garden City', 'Geneva', 'Glens Falls', 'Gloversville', 
        'Great Neck', 'Hammondsport', 'Harlem', 'Hempstead', 'Herkimer', 
        'Hudson', 'Huntington', 'Hyde Park', 'Ilion', 'Ithaca', 'Jamestown', 
        'Johnstown', 'Kingston', 'Lackawanna', 'Lake Placid', 'Levittown', 
        'Lockport', 'Mamaroneck', 'Manhattan', 'Massena', 'Middletown', 
        'Mineola','Mount Vernon', 'New Paltz', 'New Rochelle', 'New Windsor', 
        'New York City', 'Newburgh', 'Niagara Falls', 'North Hempstead', 
        'Nyack', 'Ogdensburg', 'Olean', 'Oneida', 'Oneonta', 'Ossining', 'Oswego',
        'Oyster Bay', 'Palmyra', 'Peekskill', 'Plattsburgh', 'Port Washington', 
        'Potsdam', 'Poughkeepsie', 'Queens', 'Rensselaer', 'Rochester', 'Rome', 
        'Rotterdam', 'Rye', 'Sag Harbor', 'Saranac Lake', 'Saratoga prings', 
        'Scarsdale', 'Schenectady', 'Seneca Falls', 'Southampton', 
        'Staten Island', 'Stony Brook', 'Stony Point', 'Syracuse', 'Tarrytown', 
        'Ticonderoga', 'Tonawanda', 'Troy', 'Utica', 'Watertown', 'Watervliet', 
        'Watkins Glen', 'West Seneca', 'White Plains', 'Woodstock', 'Yonkers'
      ]
    ]
    for key, values in list(zip(radios, radios[1:]))[::2]:
      flw.add_component(Label(text=(key + ":"), font_size=25))
      flw.add_component(Spacer(height=1, width=3000))
      for value in values:
        widget = RadioButton(text=value)
        widget.value = value
        widget.groupname = key
        widget.add_event_handler('clicked', self.radio_clicked)
        flw.add_component(widget)
      flw.add_component(Spacer(height=1, width=3000))
    self.add_component(flw)

    self.add_component(Spacer(height=1, width=3000))
    btn = Button(text='SUBMIT')
    btn.set_event_handler('click', self.submit)
    self.add_component(btn)

  def radio_clicked(self, **event_args):
    chosen = event_args['sender'].get_group_value()
    category = event_args['sender'].groupname
    selected.update({category:chosen})

  def submit(self, **event_args):
    print(selected)

It works according to my original requirements:



Now, forevermore, if I want to create new polls with different questions and answers, for any audience in any situation, all I need to do is change the values in the 'radios' list. I can put as many question strings, followed by a list of as many answer radio buttons (into a list which follows the syntax used in the example above), and the code above will layout the questions, all the radio buttons and separators, and the final submit button - and collect the chosen answers from the user. For my purposes, this is a tremendous win, in terms of future effort required to create these sorts of poll forms. Simply making up a list of questions and answers, is orders of magnitude faster than having to drag, drop, and configure hundreds of radio buttons on a graphic layout design!

Automating work, using code like this, is the sort of thing that saves time, energy, and money, especially in businesses, where every bit of employee time and effort costs the company money - and that savings is recouped every single time a worker needs to repeat a common routine. This is why software developers are worth so much to companies, even if their time creating code is expensive. Paying a software developer once to solve a problem or to automate a routine is typically orders of magnitude less expensive than paying employees to do the same work every day for years... And beyond that, custom software solutions which automate, simplify and check routine tasks for operational mistakes, help to reduce human error in data entry, and makes workers' lives more enjoyable, productive, and meaningful, because they don't waste so much of their daily time and energy completing boring menial tasks. The value of this effect can't be overstated, and understanding the value of even the simplest software, with this purpose in mind, is the foundation of being able to work in software development, if that's a goal that you have.

Let's review quickly what it took to get to the point of having a working poll form generator script:

  • We started by Googling how to make a single Anvil radio button, and set the properties of that button, in code.
  • We refactored the code example from the Anvil docs to fit our variable and value requirements.
  • We created a simple loop to generate radio buttons from a single list of answers, and place them on screen, using the code from the previous step.
  • We refactored code from the calculator example, so that radio buttons were placed on screen in a FlowPanel, instead of a GridPanel.
  • We integrated the working code from the previous step into another loop, which takes data from multiple question and answer lists, using some refactored code from online examples, and some code from the calculator app.
  • We created event handler methods, using some refactored code from online examples, and some code from the calculator app.
  • We went through the compose, refactor, test, edit, loop repeatedly, using the logic from small existing code fragments, to provide code structure and to satisfy detailed coding requirements.

I iterated through each of the processes above, little by little, to work out pieces of the required syntax and logic, until the code was complete. This was a bit of a different process than in the previous case studies, but it is one of the processes noted in the section entitled 'Some More Thoughts About How To Approach the Development Process'. This sort of routine is very common in creating many kinds of software development solutions. Learning to work bit by bit, composing, refactoring, and integrating pieces of example code, Google results, and code which you generate from a general understanding of how to use coding structure and tools available in Python and Anvil (loops, variable, conditional evaluations, list structures, design layout widgets and their object properties/methods, etc., as you've seen many times in this tutorial) - that's a big part of learning 'how to think in code'.

So the most important part of this project is complete (automating the creation of radio button forms from lists), but I still need to save the user's answer choices, and then display poll results in chart format, so that I can quickly visualize the tallied results of community answers.

Which UI and code components will I need to enable that final step? Well, I'll need a chart widget to display results, and that widget should appear on a separate form which only administrators can see. I can store all the user responses in a database, and since I don't want users to be able to change the poll results submitted by others, I'll put the code which interacts with the database into functions that run on the server.

Where I left off above, the answers which users selected to each question were saved to the variable 'selected', which was simply printed in the submit function:

def submit(self, **event_args):
  print(selected)

To store this response data, I'll create a database table named 'responses', with 'color', 'animal', and 'city' columns:



You've seen the add_row function used in previous examples, to add values to a row of the database. I'll put that code in an @anvil.server.callable function, which accepts color, animal, and city arguments:

@anvil.server.callable
def add_row(color, animal, city):
  app_tables.responses.add_row(
    color=color,
    animal=animal,
    city=city
  )

That server function will be called from the 'submit' function on Form1, using the 'Colors', 'Animals', and 'Cities' values from the 'selected' dictionary we already created (previously just being printed in the submit function):

def submit(self, **event_args):
  anvil.server.call(
    'add_row', 
    selected['Colors'], 
    selected['Animals'], 
    selected['Cities']
  )
  alert(f'You added: {selected}.\n\nThank you!')

OK, that saves user poll responses to the database. Now we need to display the tallied results of all the saved responses in a chart. To do that, we'll need to count the number of times users have selected any specific response, and put those totals into a format which can be plotted in a chart widget. Let's figure out how to do this by working backwards, and determining how data needs to be displayed in a plot. Search the Anvil docs for 'plots' and you'll find this example at https://anvil.works/docs/client/components/plots :

from plotly import graph_objects as go

# Plot some data
self.plot_1.data = [
  go.Scatter(
    x = [1, 2, 3],
    y = [3, 1, 6],
    marker = dict(
      color= 'rgb(16, 32, 77)'
    )
  ),
  go.Bar(
    x = [1, 2, 3],
    y = [3, 1, 6],
    name = 'Bar Chart Example'
  )
]

Looking at the bar chart example above, I can see that the 'x' list of values is used as labels along the bottom of the graph, and the 'y' values determine the size of each bar in the chart (see the image on the documentation page, and that should make perfect sense).

I'll create a new form named 'Results', and drag a plot widget onto it:



To fill the plot above with tallied data from one poll topic, I'll need to replace the 'x' and 'y' values in the example code above, with lists of counted poll responses. I'll compute those values in a server function called 'tally_plot_data' and return those lists in variables named 'count' and 'description':

def form_show(self, **event_args):
  count, description = anvil.server.call('tally_plot_data', topic)
  self.plot_1.data = [
    go.Bar(
      x = count,
      y = description,
    )
  ]

What I need the server function to do is go through each row in the 'responses' data table, collect each of the values in a single column (answers to a single question), and then count the number of times each unique response appears in that list. The first part of this is simple. You've seen this sort of routine in previous examples - I'll create a list named 'values', and use a 'for' loop to go through each row of the data table, appending every response in the column of each row that matches my topic (a single question - in this case 'color', 'animal', and 'city'):

values = []
for row in app_tables.responses.search():
  values.append(row[topic])

That gives us a list of all the results for a given question (topic). To get a list of all the unique answers, as well as a count of each of the occurrences of those answers, a quick Google search found this code at https://stackoverflow.com/questions/10741346/frequency-counts-for-unique-values-in-a-numpy-array :

import numpy as np
x = np.array([1,1,1,2,2,2,5,25,1,1])
unique, counts = np.unique(x, return_counts=True)

print(np.asarray((unique, counts)).T)
 [[ 1  5]
  [ 2  3]
  [ 5  1]
  [25  1]]

I'll move the import to the beginning of my server module, and refactor the 1 line of code I need, to match the variables I'm using:

count, description = np.unique(values, return_counts=True)

Combining all the code above into a single function which accepts a topic name argument, and returns the 'count' and 'description' variable values I need for the plot, this is the result:

@anvil.server.callable
def tally_plot_data(topic):
  values = []
  for row in app_tables.responses.search():
    values.append(row[topic])
  count, description = np.unique(values, return_counts=True)
  return count, description

Now I just need to take the code in the 'submit' function on Form1, which calls the server function above (we created that earlier), and repeat it for each of the poll question topics I want to tally and chart. To do this, I'll wrap the existing code in a 'for' loop, with a 4 second pause and an alert for the user to continue after each plot display:

def form_show(self, **event_args):
  for topic in ('color', 'animal', 'city'):
    count, description = anvil.server.call('tally_plot_data', topic)
    self.plot_1.data = [
      go.Bar(
        x = count,
        y = description,
        name = topic
      )
    ]
    time.sleep(4)
    alert('Click OK to continue')

And with that, I get 3 plots displaying responses to each question, with the tallied counts of each answer represented as bar chart graphics:







On Form1, I'll need some code to navigate to this page. I could use the password routine from the shift clock example, but instead I'd prefer to use the user management service in Anvil, so that admins can just log in with Google (so they don't have to remember another password). I'll add a link on Form1 to navigate to the Results page, and add the code below to its click handler. This code checks if the user is logged in. If not, if presents the login form, with the option to sign up for an account. If the login email matches the admin email (we could also check to see if it's in a list of emails), then the Results form is opened. Otherwise, the user is alerted with a message that the results page is only for admin users:

def link_1_click(self, **event_args):
  login = anvil.users.get_user()
  if None == login:
    anvil.users.login_with_form(
      show_signup_option=True, 
      allow_cancel=True
    )
    self.link_1_click()
  else:
    if login['email'] == 'user1@gmail.com':
      open_form('Results')
    else:
      alert('Sorry, this area is for admin users.')



With that, all of our original requirements for the app are complete. The full code for Form1 is:

from ._anvil_designer import Form1Template
from anvil import *
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
# anvil.users.logout()

radios = [
      'Colors', [
        'White', 'Yellow', 'Blue', 'Red', 'Green', 'Black', 'Brown', 'Azure',
        'Ivory', 'Teal', 'Silver', 'Purple', 'Navy blue', 'Pea green', 'Gray',
        'Orange', 'Maroon', 'Charcoal', 'Aquamarine', 'Coral', 'Fuchsia', 
        'Wheat', 'Lime', 'Crimson', 'Khaki', 'Hot pink', 'Magenta', 'Olden', 
        'Plum', 'Olive', 'Cyan'
      ],
      'Animals', [
        'dog', 'cat', 'lion', 'bear', 'monkey', 'turtle', 'mouse', 'hawk', 
        'dolphin', 'fly', 'snake', 'deer', 'aardvark', 'scorpion', 'chipmunk',
        'squirrel', 'horse', 'rat', 'pigeon', 'lizard', 'elephant', 'pig',
        'guinea pig', 'swan', 'duck', 'goose', 'chicken', 'turkey', 'cow'
      ],
      'Cities', [
        'Albany', 'Amsterdam', 'Auburn', 'Babylon', 'Batavia', 'Beacon', 
        'Bedford', 'Binghamton', 'Bronx', 'Brooklyn', 'Buffalo', 'Chautauqua', 
        'Cheektowaga', 'Clinton', 'Cohoes', 'Coney Island', 'Cooperstown', 
        'Corning', 'Cortland', 'Crown Point', 'Dunkirk', 'East Aurora', 
        'East Hampton', 'Eastchester', 'Elmira', 'Flushing', 'Forest Hills', 
        'Fredonia', 'Garden City', 'Geneva', 'Glens Falls', 'Gloversville', 
        'Great Neck', 'Hammondsport', 'Harlem', 'Hempstead', 'Herkimer', 
        'Hudson', 'Huntington', 'Hyde Park', 'Ilion', 'Ithaca', 'Jamestown', 
        'Johnstown', 'Kingston', 'Lackawanna', 'Lake Placid', 'Levittown', 
        'Lockport', 'Mamaroneck', 'Manhattan', 'Massena', 'Middletown', 
        'Mineola', 'Mount Vernon', 'New Paltz', 'New Rochelle', 'New Windsor', 
        'New York City', 'Newburgh', 'Niagara Falls', 'North Hempstead', 
        'Nyack', 'Ogdensburg', 'Olean', 'Oneida', 'Oneonta', 'Ossining', 'Oswego',
        'Oyster Bay', 'Palmyra', 'Peekskill', 'Plattsburgh', 'Port Washington', 
        'Potsdam', 'Poughkeepsie', 'Queens', 'Rensselaer', 'Rochester', 'Rome', 
        'Rotterdam', 'Rye', 'Sag Harbor', 'Saranac Lake', 'Saratoga Springs', 
        'Scarsdale', 'Schenectady', 'Seneca Falls', 'Southampton', 
        'Staten Island', 'Stony Brook', 'Stony Point', 'Syracuse', 'Tarrytown', 
        'Ticonderoga', 'Tonawanda', 'Troy', 'Utica', 'Watertown', 'Watervliet', 
        'Watkins Glen', 'West Seneca', 'White Plains', 'Woodstock', 'Yonkers'
      ]
    ]

selected = {}

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def form_show(self, **event_args):
    flw = FlowPanel()
    for key, values in list(zip(radios, radios[1:]))[::2]:
      flw.add_component(Label(text=(key + ":"), font_size=25))
      flw.add_component(Spacer(height=1, width=3000))
      values.sort()
      for value in values:
        widget = RadioButton(text=value)
        widget.value = value
        widget.groupname = key
        widget.add_event_handler('clicked', self.radio_clicked)
        flw.add_component(widget)
      flw.add_component(Spacer(height=1, width=3000))
    self.add_component(flw)

    self.add_component(Spacer(height=1, width=3000))
    btn = Button(text='SUBMIT')
    btn.set_event_handler('click', self.submit)
    self.add_component(btn)

  def radio_clicked(self, **event_args):
    chosen = event_args['sender'].get_group_value()
    category = event_args['sender'].groupname
    selected.update({category:chosen})

  def submit(self, **event_args):
    anvil.server.call(
      'add_row', 
      selected['Colors'], 
      selected['Animals'], 
      selected['Cities']
    )
    alert(f'You added: {selected}.\n\nThank you!')

  def link_1_click(self, **event_args):
    login = anvil.users.get_user()
    if None == login:
      anvil.users.login_with_form(
        show_signup_option=True, 
        allow_cancel=True
      )
      self.link_1_click()
    else:
      if login['email'] == 'user1@gmail.com':
        open_form('Results')
      else:
        alert('Sorry, this area is for admin users.')

The code for the Results page is:

from ._anvil_designer import ResultsTemplate
from anvil import *
import plotly.graph_objects as go
import anvil.server
import anvil.google.auth, anvil.google.drive
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import time

class Results(ResultsTemplate):

  def __init__(self, **properties):
    self.init_components(**properties)

  def form_show(self, **event_args):
    for topic in ('color', 'animal', 'city'):
      count, description = anvil.server.call('tally_plot_data', topic)
      self.plot_1.data = [
        go.Bar(
          x = count,
          y = description,
          name = topic
        )
      ]
      time.sleep(4)
      alert('Click OK to continue')

The server functions are:

import anvil.google.auth, anvil.google.drive, anvil.google.mail
from anvil.google.drive import app_files
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server
import numpy as np

@anvil.server.callable
def add_row(color, animal, city):
  app_tables.responses.add_row(
    color=color,
    animal=animal,
    city=city
  )

@anvil.server.callable
def tally_plot_data(topic):
  values = []
  for row in app_tables.responses.search():
    values.append(row[topic])
  count, description = np.unique(values, return_counts=True)
  return count, description

A working version of the current app is available at http://mypoll.anvil.app.

26.5 Count Words in a Text Area

Let's take a break and try a very simple example. I want to create an app which counts the number of words in text entered by the user. See if you can figure how to do this yourself, before you look at the case study!

Here's my thought process:

  1. I need a place for the user to type text. When the user submits the string of text, the program needs to split that string into a list of words, and count the number of words in the list, then display that computed value.
  2. I can use a text area widget to allow the user to enter text, and a button to begin the count computation. I'll display the result of the count computation with an alert() function.

Here's the user interface, with the widgets described above:



I'll execute the string-splitting/word-counting computation in the button_1_click method. If you have no idea how to perform the calculation, try Googling 'python count words in string'. You'll find lots of answers. I'll choose to use the '.split' method upon the entered string, which creates a Python list structure containing the individual words in the string. Then I'll use the Python len() function to count the words in that list. Here's some pseudo-code to think the process through:

def button_1_click(self, **event_args):
  # Set the text property of text_area_1.text to the variable 'my_text'.
  # Apply the Python '.split()' method to the 'my_text' string, and assign
  #   the results to the variable 'words'.
  # Apply the 'len()' function to the 'words' value, and assign the variable
  #   'count' to the result.
  # Alert the user with a formatted string displaying the 'count' value.

Converting the first line of pseudo-code above to Python:

my_text = self.text_area_1.text

The second line:

words = my_text.split()

The third line:

count = len(words)

The fourth line:

alert (f'Number of words in your text:\n\n{count}')

I prefer to refactor the code above so that it's combined into a shorter few lines, eliminating several variables along the way:

def button_1_click(self, **event_args):
  words = self.text_area_1.text.split()
  alert(f'Number of words in your text:\n\n{len(words)}')

You could actually combine the 2 lines above into a single line. Before looking at the code below, can you figure out how to eliminate the 'words' variable? Here's the solution I came up with:

def button_1_click(self, **event_args):
  alert(f'Number of words in your text:\n\n{len(self.text_area_1.text.split())}')

Do you prefer using 1, 2, or 4 lines? Which do you think is easiest for others to follow, if they're just reading your code for the first time and trying to determine what it does? For my tastes, the 2 line version works nicely. Even though the 1-line version above is more concise, it's a bit harder to read - and the 4 line version seems to be more wordy than necessary. To me, the 2 line version reads like English: "'words' is the text in text_area_1, split apart. Alert the user with the length of that word list" - that sounds simple to me. Here's the final code I choose to use for Form1:

from ._anvil_designer import Form1Template
from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)

  def button_1_click(self, **event_args):
    words = self.text_area_1.text.split()
    alert(f'Number of words in your text:\n\n{len(words)}')

It satisfies our original requirements:



You can run it live at:

https://wordcount.anvil.app

26.5.1 Why Such a Simple Example Here?

Of course, there are many existing ways you may imagine being able to count the words in a string of text. You could just use that feature in a word processor, for example, if your only objective is to count some words. But remember, what you're doing here is learning how to implement that sort of feature in software of your own. So, for example, if you want to check that entries made by users contain a minimum number of words - perhaps if you're creating a test taking application for a school class, or if you're creating a profile page for a web site - you can incorporate this sort of functionality in any sort of larger custom app that requires it.

Try coming up with a few simple tasks like this example, and see if you can work through the entire development process on your own, using some of the outline approaches you've seen in these case studies. Perhaps take some individual menu features from a piece of software you use, and see if you can duplicate their functionality in Anvil, then add your own twist. For example, can you figure out how to extend the word count example above to disable the Submit button until the user has typed 20 words, and display a tooltip which pops up to tell the user they need to type at least 20 words? (hint: there is a tooltip property for button widgets, and you could run the word count computation whenever text in the text_area_1 changes, or by a timer, ...).

Look in some apps you use for ideas about how to practice developing user interfaces, data storage routines (using database tables, lists, dictionary, and variable structures), looping routines which perform operations on each item in a list, conditional evaluations, etc. You know enough about how Anvil works and how to look up documentation and to Google Python code examples which can be refactored for your specific needs, to begin exploring app development on your own. The more examples and tutorials you read, the larger your awareness of available tools will grow, and the better your understanding of how to use those tools for your needs will improve. Read examples and experiment with existing code. Try creating little software features which you see in examples, and which you imagine by extrapolating variations on those examples. When you have trouble, ask questions in online forums, on Reddit, Stackoverflow, etc. The goal is to start thinking and working on your own, and writing little bits of code line by line. Stick with little examples like this case study at first, and build features organically by experimenting and planning changes to small working functional parts of an app that can be used elsewhere. Get really good at basic routines such as using text entered in text_box widgets and values selected from drop_down widgets - think of how many ways just those 2 basic UI components have been used in the examples from this tutorial so far. Get used to thinking about storing every bit of information in your apps in database tables, and try building lots of simple apps which make use of them: build yourself quick shopping list apps, notepad apps, scheduling apps, to-do list apps, favorite movie apps - anything that gets you storing, retrieving, searching, sorting, editing, and manipulating some sort of useful information in your life. If you have a business, think of anything that you use spreadsheets, calculators, paper and pencil, or calendars to accomplish routinely. Can you imagine taking some data or some computation routine from a spreadsheet you've created, and improve on that functionality by building an Anvil app to perform the same task more efficiently? Can you perhaps make an email app that sends messages to a group, or autoresponds to incoming messages in some way that saves you time? Little projects like this are a great way to start building some simple app functionality which is actually useful to you, and when you begin to implement some time-saving app, you'll almost certainly come up with features that will improve its functionality, as you use it. Start small with some very simple case study, and conceive additional features as you conceive extensions to the original idea. Once you start doing this, you'll see opportunities for app development everywhere!

26.6 Typing Tutor

I want to create an app that displays words to be typed by the user, to help practice and improve typing speed. I'll use the description/outline/pseudo-code process again, to work out how to build it.

  • Description: The app should display consecutive words from a random selection of the English language. The user should be able to select the number of words to practice in a session. There should be a large display of the current word, and a place for the user to type the displayed word. When the user has completed a given number of words, the app should display the typing speed the user achieved, as a words-per-minute value.
  • Design Components: We can use a label widget with a large font to display the current word. We can use a drop_down widget to allow the user to select a number of words to practice. We can use a text_box widget to accept characters typed by the user. We can use another label widget to display the words-per-minute results. We can store the words in a list data structure that loads when the program starts. We're going to need to look for a source of these words, and convert them into a Python list structure.

Ok let's drag-and-drop the widgets we've described above onto a new Anvil form design:



Set the numbers in drop_down_1 'items' property to be: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 100.

Now, what needs to happen when the program starts, and when the user interacts with each of the widgets? Well, when the program starts, we'll need to load the list of English words to choose from. When the user clicks the drop_down_1 widget, the selected number of random words should be picked from that word list, and displayed successively in the label_1 widget. The start time should be saved to a variable, to be used later for calculating the total time and speed at which the user types. When the user types in the text_box_1 widget, the text in that widget should be compared to the text in label_1. If they match, then the next word should be displayed in label_1. When all the words have been typed correctly, the result of a calculation determining total time, and typing speed in words per minute, should be displayed in label 2. We can put all this code in the drop_down_1_change method, because the process will be started when the user selects a number of words to be displayed, from the drop_down_1 selector. Here's some pseudo-code to help make all that happen:

def drop_down_1_change(self, **event_args):
  # Create an empty list named 'words', to store the randomly selected words.
  # Set a variable 'number_of_words' to hold the number selected by the user.
  # Get the list of random words from a server function.  We want to handle
  #   this operation with a server function, because loading tens of 
  #   thousands of words into the user's browser code could slow down the 
  #   user interface to a crawl.  We'll call the server function 'rand_word'
  #   and pass it the number of random words to get as an argument.  We'll
  #   set the results of this function to the variable 'words'.
  # Focus the cursor on the text_box_1 widget, so the user can begin typing.
  # Save the start time to the variable 'starttime'.
  # Use a 'for' loop to go through each word in the 'words' list.
  #   For each word, use another loop to continually check if the text in 
  #   text_box_1 (where the user is typing), matches the current word.
  #   If the word has been typed correctly, clear the text in text_box_1,
  #   and continue with the next word.
  # When all the words have been typed correctly (i.e., when the loop above
  #   has been completed), set the end time to the variable 'endtime'.
  # Subtract the start time from the end time, and assign that value to the
  #   variable 'totaltime'
  # Calculate the number of words per minute by dividing the number of
  #   words typed by (total seconds * 60).  Assign this words per minute
  #   value to the variable 'words_per_minute'.
  # Alert the user with the words_per_minute value.

Now let's convert the pseudo-code above into some working Python. Here's the code needed to create an empty 'words' list:

words = []

Now set the number_of_words variable to the value selected in drop_down_1. Be sure to convert that number to an integer value:

number_of_words = int(self.drop_down_1.selected_value)

Now call the function on the server to get our random list of words (we haven't created that function yet, but we can write the code to call it):

words = anvil.server.call('rand_word', number_of_words)

Focus the cursor on text_box_1 and set the 'starttime' variable:

self.text_box_1.focus()
starttime = datetime.now()

Use a 'for' loop to go through each word in the 'words' list returned by the server function above:

for word in words:

For each word, set the text property of the label at the top of the screen (where the font size has been set to 30), to the current random word:

self.label_words.text = word

Use a 'while' loop to check whether the text property of text_box_1 matches the current random word. If it doesn't match, just continue running the while loop (this will stop as soon as the typed word matches the 'word' string):

while word != self.text_box_1.text:
  pass

Now clear text_box_1 so the user can begin typing the next random word in the 'words' list:

self.text_box_1.text = ''

When the 'for' loop has been completed, that means that all the words have been typed correctly by the user, so set the 'endtime' variable to the current time:

endtime = datetime.now()

Subtract 'starttime' from 'endtime' to compute the 'totaltime' the user took to type all the words correctly:

totaltime = endtime - starttime

Compute the words_per_minute calculation as decribed in the pseudo-code:

words_per_minute = (number_of_words / totaltime.seconds) * 60

Format the computation result above to 2 decimal places, along with some descriptive text, and display this message in the label_2 widget:

self.label_2.text = f'DONE! Your speed was: {words_per_minute:.2f} words per minute.'

Assemble all the code above into the drop_down_1_change method... and the front end of this is complete:

def drop_down_1_change(self, **event_args):
  words = []
  number_of_words = int(self.drop_down_1.selected_value)
  words = anvil.server.call('rand_word', number_of_words)
  self.text_box_1.focus()
  starttime = datetime.now()
  for word in words:
    self.label_words.text = word
    while word != self.text_box_1.text:
      pass
    self.text_box_1.text = ''
  endtime = datetime.now()
  totaltime = endtime - starttime
  words_per_minute = (number_of_words / totaltime.seconds) * 60
  self.label_2.text = f'DONE! Your speed was: {words_per_minute:.2f} words per minute.'

Now we just need a server function to select and return a given number of random practice words from our enormous list of the English language. We still haven't create that 'words' list, but you have seen how to pick random choices from a list, so let's start by writing the code to do that. Remember to import the 'random' library:

random.choice(words)

We can create an empty list to hold the list of randomly selected words:

random_words = []

We can use the '.append' method of Python lists to combine the above 2 lines of code, adding the random choice above, to the list:

random_words.append(random.choice(words))

Now enclose that line in a 'for' loop, which runs the number of times sent to the function (the number selected from the drop_down_1 widget in the front end code above - referred here by the argument variable 'num'). Return the list generated by that 'for' loop, and we've got a function which does what we need:

@anvil.server.callable
def rand_word(num):
  random_words = []
  for i in range(num):
    random_words.append(random.choice(words))
  return random_words

Finally, some work needs to be done to get a collection of English language words into a list data structure that's usable by Python. Googling 'list of English words' brings us to a file called 2of12full.txt at https://sourceforge.net/projects/wordlist/files/Alt12Dicts/2016.06.26/ . Each line of that file contains 5 columns of information about the word, and then the word. There are more than 53,000 lines like that in the file, which look something like this:

9:  9  -#  -&  -=   A
12: 12  -#  -&  -=   a
5:  5  -#  -&  -=   AAA
9:  9  -#  -&  -=   aardvark
4:  4  -#  -&  -=   Aaron
7:  2  5#  -&  -=   abaci
12: 12  -#  -&  -=   aback
12: 12  -#  -&  -=   abacus
6:  6  -#  -&  -=   abaft
8:  8  -#  -&  -=   abalone
12: 12  -#  -&  -=   abandon

I just want the words in each row, and none of the other data, converted into a Python list (with quotes around each word, and separated by commas). To do this, I can read the lines of the file into a variable and use a 'for' loop to assign each individual line of that file to a string variable, using the '.split()' method on that string, and picking the 6th item in the resulting list created by the split operation, and append that value from each of the lines, to a 'words' list. Here's the code I came up with:

words = []
f = open("2of12full.txt", "r")
wordlines = f.readlines()
for line in wordlines:
  x = line.split()
  word = x[5]
  words.append(word)
print(words)

The resulting data looks like this, with 53,000+ entries:

['A', 'a', 'AAA', 'aardvark', 'Aaron', 'abaci', 'aback', 'abacus', 'abaft',
 'abalone', 'abandon', ...]

I copied that list with all 53,000+ entries into my server code from earlier, so it looks like this:

import anvil.server
import random

# ALL 53000+ ENTRIES GO HERE:

words = ['A', 'a', 'AAA', 'aardvark', 'Aaron', 'abaci', 'aback', 'abacus', 
         'abaft', 'abalone', 'abandon', ...]

@anvil.server.callable
def rand_word(num):
  random_words = []
  for i in range(num):
    random_words.append(random.choice(words))
  return random_words

Because this code runs on the server, none of that huge list of data ever gets sent to the user's browser - only the few random words chosen by the rand_word function ever get returned to the browser. This keeps the program running quickly, even on small mobile devices with little processing power.

And with that, we've got a working Typing Tutor app:



You can see it run live at:

https://typing.anvil.app

Can you think of any features you'd like to add to this app?

26.7 More Case Studies

(To-Do: Please send any requests for more case studies to nick@com-pute.com)

27. Why is all this so Valuable - Even If You're Not A Professional Developer?

Read this.

28. This is Just the Beginning! Where to go from here...

At this point, you've learned a lot about how to create apps with Anvil. If you've never written a line of code before reading this tutorial, you're likely a bit overwhelmed. It may feel initially impossible to remember the many small code details, to internalize everything, and to become fluent, so that you can just create any app functionality you want.

The best way forward at this point is to simply press ahead, and continue reading and immersing yourself in all the tutorials at https://anvil.works . The examples there are a lot of fun, they're filled with practical demonstrations of useful apps, and most lines of code are fully explained. You'll see a variety of all sorts of applications that satisfy a wide range of common computing needs, and more than anything, you'll step through many of the same sorts of code snippets you've seen in this tutorial, re-enforcing everything repeatedly. There are plenty of bigger apps with deeper code dives.

Once you've completed the tutorials, read through the Anvil 'Docs'. Every bit of the Anvil IDE and coding API is fully explored there, with many more complete applications thoroughly explained.

When you're finished reading through the full Anvil Documentation, read through the Anvil blog. There are several dozen more interesting application examples and explanations to dive into there, as well as case studies and other useful info.

And when you're looking to dive even more deeply into additional code, search through the Anvil forum. You'll find many more code examples and answers to questions there, to help learn and find solutions to programming challenges with Anvil.

Anvil's documentation is absolutely fantastic. If you just follow along, it will lead you through to a functional understanding of how to accomplish most common programming goals. If you're not sure you understand everything in this tutorial, don't let that slow you down. You'll become intimately familiar with the sorts of code examples you've seen here, simply by exposing yourself to more code. Just dive in and go through the motions of completing more tutorials and reading more docs. Search for Anvil videos on YouTube, and experiment with code examples to see how your changes affect parts of an app. Remember, you can always save versions of any app, so you don't need to worry about breaking things.

Also remember that the Anvil IDE itself, with built in code snippets and autocomplete, is a great help while you're writing code. When you sit down to a blank screen to start a new app, design a user interface, and then add code to 'click' methods, to the __init__ method, etc., and let autocomplete help you remember which objects and properties are available to your design. You'll find that this becomes absolutely intuitive once you're past the basic understanding in this tutorial. Finally, remember to use Google to find answers to questions about any general Python coding task. You'll likely find that every question you could possibly come up with has been answered many times, in many different ways online, and a quick Google search will lead you straight to the answers you need.

It should be fun to play with bits of code, and satisfying to watch your skills grow. You'll come out the other side of the initial learning curve a capable developer, able to make apps to satisfy computing needs of all sorts!

28.1 If You Want More Basic Info About Computing

If you feel like you need some more basic information about the fundamentals of computing - what 'bits', 'bytes', 'operating systems', 'platforms', 'language ecosystems', and all the rest of the terms mean, read:

www.com-pute.com

That tutorial will give you a broad overview and general introduction to how all the layers of the modern computing stack work together, and maybe some peace of mind about what needs to be learned, to take part in technology in the modern world.

29. About the Author

Nick Antonaccio has been writing software code since 1979. Google 'Learn Livecode', 'Learn Rebol', 'Learn NS Basic', 'Learn RFO', and you'll see the top results are all by Nick. He has written mission critical software for many high profile corporations, and his industry-leading Merchants' Village POS Consignment Software has been used to sell tens of millions of products at busy retail locations around the world.

https://merchantsvillage.com/About%20The%20Author.html

To hire Nick for Anvil tutoring, or if you'd like a software product developed, please call/text 215-630-6759 or send a message to nick@com-pute.com

8-Jul-2022