📜 ⬆️ ⬇️

When GitHub shoots you in the head, a new framework is created. The idea, concept and implementation of "Rutetider"


Hi, Habrahabr! Ready-made architectural solution for mobile devices, including iOS , Android , Telegram-bots , as well as platforms that support the processing of http requests , acting as a pet-project of the author of the article, will be interesting to those who want to implement a “pocket” schedule of classes for their universities and schools.

Content of publication:


Introduction


In order to contribute to the open-source community for the most part and to a lesser extent - in order to solve the problem of the unavailability of the university’s class schedule on mobile devices (in truth, accessibility, but extremely non-adaptive and “long”) - I had to take the best opportunity - write a Telegram-bot (if you're interested - an article on Habrahabr ), and to solve the problem not only for your university - a small framework.
')

It was decided to base the framework on the first solution, with the same tools as for the bot, but not to exclude the possibility of development on platforms that directly support the integrity of mobile applications — iOS, Android, and in general on any other platforms (web an application with adaptive layout for phones, for example).

Simply put, it was determined two types of access to the functional - REST-API and Python-library for programmers using directly Python.

And Rutetider


This is a set of methods and tools based on a template sequence that will allow you to create, perhaps not a flexible, but certainly working application . First of all, this is a “here and now” solution; if the main goal is development - write everything from scratch on your own and do not use the framework.

Another positive point is the available documentation , which is filled not only with explanations of the work, but also with illustrations and instructions that significantly speed up understanding and development.

Framework architecture


The basic principle


As mentioned above, it is very difficult, without having a lot of programming experience in general, to determine the correct and beautiful structure, so I had to lean against something sample, but with its pluses - quite obvious and working.


Speaking on behalf of the user, he will need to go through a series of screens: the choice of the main option (the ability to get a schedule ) among the many possible others, the faculty , the course , then the group and the date itself (also among the many other useful features).

From the scheme it should be clear that the programmer himself needs to “catch” the user's location and display the necessary menus with different content, as well as keep statistics if such a condition is worth it.


More on the necessary methods


In order not to break away from the context, we’ll continue with a friend - it’s necessary to record the user's position on the platforms without the possibility of using any local storage (such as the user's phone), because the “Back” button does not know where to return, it needs "Feed" the same position. Another example is to know what kind of data a student enters, then to determine the group by faculty and course, and select the schedule for the relevant date by group.

In addition, the programmer can count on convenient work with dates for today and tomorrow, that is, there is an opportunity to make both accurate and relevant values, and get it.

While we stopped at entering data, it is worth mentioning that the framework has methods that are ready to help further structure information about couples in universities - from the audience and time to the teacher's data.

Keep an example of adding lecture options:

from rutetider import Timetable timetable = Timetable(database_url) timetable.add_lesson('IT', '3', 'PD-31', '18.10', '', '451', '2', ' ..') # params: faculty, course, group_name, lesson_date, lesson_title, # lesson_classroom, lesson_order, lesson_teacher 

I still don't understand how this works.


I tried to add a bit of modularity to the tools so that some platforms could not use unnecessary functionality, but on the other side I “handcuffed” everyone who wanted to use “Rutetider” - the presence of a server (most likely) and a database.

The need to create a database is caused by the fact that the author does not have enough resources to provide everyone with free space for his schedule and other valuable information, so the programmer will have to create his own PostgreSQL and share a link to access (fortunately, there are a lot of free features, I’ll have one I am telling here ).





But searching for a server may not be necessary for someone, but it will definitely be necessary for those whose university updates the schedule every day or every week - in this case, creating a tool for entering the schedule by means of a parser, reading CSV or any convenient method is a must .

And here we are all very lucky, because the information technology community supports developers: Heroku Cloud Platform for Python, Java, Node.js and Firebase , Parse , Polljoy - iOS (the author did not use most of the sentences; if you have any additions or comments to this account - please inform).

What functionality can be expected


Lectures and couples - a component of the overall structure, responsible for working with the processing of classes. If you saw an example of adding pairs, then look at getting them.

 schedule = timetable.get_lessons('PD-31', '18.10') # params: group_name, lesson_date print(schedule) # {'lessons': { # '3': {'lesson_teacher': ' ..', 'lesson_classroom': # '451', 'lesson_order': '3', 'lesson_title': ''}, # '1': {'lesson_teacher': ' ..', 'lesson_classroom': '118', # 'lesson_order': '1', 'lesson_title': #''}, # '2': {'lesson_teacher': ' ..', 'lesson_classroom': '200', # 'lesson_order': '2', 'lesson_title': #' '}}} 

Subscription , but not to notifications, which may well be a useful feature in the future when the framework is relevant, but to receive a schedule with just one click.


Due to the fact that the architecture requires you to press several buttons and see as many screens in front of you and choose something, this functionality is extremely useful - the user must subscribe to a certain group once and he will no longer have to “steam”.

Swift code
 import UIKit class ViewController: UIViewController { fileprivate let databaseURL = "postgres://nwritrny:VQJnfVmooh3S0TkAghEgA--YOxoaPJOR@stampy.db.elephantsql.com:5432/nwritrny" fileprivate let apiURL = "http://api.rutetiderframework.com" @IBAction func subscribeAction(_ sender: Any) { let headers = ["content-type": "application/x-www-form-urlencoded"] let postData = NSMutableData(data: "url=\(databaseURL)".data(using: .utf8)!) postData.append("&user_id=1251252".data(using: .utf8)!) postData.append("&group_name=PD-3431".data(using: .utf8)!) let request = NSMutableURLRequest(url: NSURL(string: "\(apiURL)/subscribers/add_subscriber")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "PUT" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() } @IBAction func getSubscriptionInfoAction(_ sender: Any) { let headers = ["content-type": "application/x-www-form-urlencoded"] let postData = NSMutableData(data: "url=\(databaseURL)".data(using: .utf8)!) postData.append("&user_id=1251252".data(using: String.Encoding.utf8)!) let request = NSMutableURLRequest(url: NSURL(string: "\(apiURL)/subscribers/get_subscriber_group")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error) } else if let jsonData = data { do { let json = try JSONSerialization.jsonObject(with: jsonData) as? Dictionary<String, Any> print(json?["group"]) } catch let error{ print(error) } } }) dataTask.resume() } } 


Current dates with the possibility of making and receiving a schedule for today and tomorrow.

 import requests import json api_url = 'http://api.rutetiderframework.com' database_url = 'postgres://nwritrny:VQJnfVmooh3S0TkAghEgA--YOxoaPJOR@stampy.db.elephantsql.com:5432/nwritrny' #   ,           r = requests.post(api_url + '/currentdates/', data=json.dumps({ 'url': database_url}), headers={'content-type': 'application/json'}) print(r.status_code) # 200 #      ,     , #     . r = requests.put('http://api.rutetiderframework.com/currentdates/add_current_dates', data=json.dumps({ 'url': database_url, 'today': '07.04', 'tomorrow': '08.04'}), headers={'content-type': 'application/json'}) r = requests.post('http://api.rutetiderframework.com/currentdates/get_current_dates', data=json.dumps({ 'url': database_url}), headers={'content-type': 'application/json'}) print(r.json()) # {'dates': ['07.04', '08.04']} 

Important, but no less difficult for the initial understanding, the point is the user's position - because of the impossibility of using built-in or other convenient means.


For example, if a user selects a group, then we need to know what choice the user has already made (faculty and course), and if he has made a wrong course, then react to pressing the “Go back” button.

 @bot.message_handler(func=lambda mess: ' ' == mess.text, content_types=['text']) def handle_text(message): user_position = UserPosition(database_url).back_keyboard(str(message.chat.id)) if user_position == 1: UserPosition(database_url).cancel_getting_started(str(message.chat.id)) keyboard.main_menu(message) if user_position == 2: UserPosition(database_url).cancel_faculty(str(message.chat.id)) keyboard.get_all_faculties(message) if user_position == 3: UserPosition(database_url).cancel_course(str(message.chat.id)) faculty = UserPosition(database_url).verification(str(message.chat.id)) if faculty != "Ń– Ń–Ń–" and faculty != ' ': keyboard.stable_six_courses(message) if faculty == "Ń– Ń–Ń–": keyboard.stable_one_course(message) if faculty == " ": keyboard.stable_three_courses(message) if user_position == 4: UserPosition(database_url).cancel_group(str(message.chat.id)) faculty, course = UserPosition(database_url).get_faculty_and_course(str(message.chat.id)) groups_list = Timetable(database_url).get_all_groups(faculty, course) groups_list.sort() keyboard.group_list_by_faculty_and_group(groups_list, message) 

Going back one menu is a little more complicated, so let's break it down into a diagram.

To know which menu the user needs, if he wants to go back, we need to use the “back_keyboard” method, which will tell you what position the user has stopped at. The diagram shows that the position is equal to one (1) - a digit denoting the sequence number of the menu where the user is “stuck”, which means that you need to return to the index position zero (1-1 = 0). And once again: the index - which menu is the last but one, the user's position - which menu now. How you display the menu and where you store it is the business of your application, but getting the position is already the work of the framework.

The final part of the architecture is statistics , there is nothing complicated here, but a lot of useful stuff. For example, you can easily keep detailed statistics of your application - record the number of faculty chosen by users, and then easily get this number and display it in any admin panel.

Swift code
 func initializeDatabase() { let request = NSMutableURLRequest(url: NSURL(string: "\(apiURL)/statistics/")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: callback) dataTask.resume() } func addStatistic() { let body = ["url": databaseURL, "user_id": "1251252", "point": "faculty", "date": "06.04.2017"] var jsonBody: Data? do { jsonBody = try JSONSerialization.data(withJSONObject: body) } catch { } let request = NSMutableURLRequest(url: NSURL(string: "\(apiURL)/statistics/add_statistics")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "PUT" request.allHTTPHeaderFields = headers request.httpBody = jsonBody let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: callback) dataTask.resume() } func getStatistic() { let body = ["url": databaseURL, "user_id": "1251252"] var jsonBody: Data? do { jsonBody = try JSONSerialization.data(withJSONObject: body) } catch { } let request = NSMutableURLRequest(url: NSURL(string: "\(apiURL)/statistics/get_statistics_general")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = jsonBody let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: callback) dataTask.resume() } func callback(_ data: Data?, _ resp: URLResponse?, _ error: Error?) { printResponse(resp, error: error) parseResponse(data) } func parseResponse(_ data: Data?) { if let jsonData = data { do { let json = try JSONSerialization.jsonObject(with: jsonData) as? Dictionary<String, Any> print(json ?? "json is nil") } catch let error{ print(error) } } } func printResponse(_ response: URLResponse?, error: Error?) { if (error != nil) { print(error!) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse ?? "response is nil") } } 


thank


I hope that you not only appreciated my approach to describing the work done and the flow of thoughts in general, but also showed a deeper interest. And if you are completely fascinated, I will be happy to answer your questions or help with the development of my part.

Source: https://habr.com/ru/post/326222/


All Articles