/ app.py
app.py
  1  from elements.core import Bottle, route, run, chevron_template, TEMPLATE_PATH, static_file, request, redirect
  2  from elements.storage import save
  3  from elements.posts import load_posts, SolarPost
  4  from elements.accounts.sessions import load_session
  5  from elements.api.accounts import login, logout
  6  from collections import defaultdict
  7  from datetime import datetime, timedelta
  8  import csv
  9  import os
 10  
 11  ##################################################
 12  ### Planet Data ##################################
 13  ##################################################
 14  
 15  app = Bottle()
 16  path = '/credenso/' # How to get to this module from the root
 17  name = "Credenso"
 18  description = "Café and Community"
 19  thumbnail = path + 'static/images/cred.png'
 20  
 21  # If a planet is not defined as a station, it will still be available but
 22  # its tag will not be shown on the main hub.
 23  station = True
 24  
 25  # This value refers to the folder that this file is stored in.
 26  here = os.path.dirname(os.path.realpath(__file__))
 27  
 28  # By appending our views folder to the template path,
 29  # We can render them with the template() functions.
 30  TEMPLATE_PATH.append(f'{here}/views')
 31  
 32  # Defaults are the values which are passed to every template
 33  # in the current scope
 34  def defaults(additional_data=None):
 35      default_data = {
 36              'path': path,
 37              'static': path + 'static/'
 38      }
 39  
 40      # if there's extra data, (over)write it to the default info.
 41      if additional_data is not None:
 42          for key, value in additional_data.items():
 43              default_data[key] = value
 44  
 45      return {
 46              # This is the path from the Solar root directory
 47              'partials_path': f'{ here }/views{path}components/',
 48  
 49              # This is the filetype we use for components
 50              'partials_ext': 'mo',
 51  
 52              # This is the data that gets rendered into the template
 53              'data': default_data
 54      }
 55  
 56  def slugify(string):
 57      # filesafe name
 58      s = string.lower().strip()
 59      s = re.sub(r'[^\w\s-]', '', s)
 60      s = re.sub(r'[\s_-]+', '-', s)
 61      s = re.sub(r'^-+|-+$', '', s)
 62  
 63      return s
 64  
 65  ##################################################
 66  ### API Section ##################################
 67  ##################################################
 68  
 69  # we register certain paths to preconfigured endpoints
 70  # for use within the application
 71  @app.post('/menu')
 72  def menu_post():
 73      session = load_session()
 74      try:
 75          member = session.get('member')
 76      except AttributeError:
 77          # If getting the member fails, redirect to login
 78          return redirect('/login')
 79  
 80      post = SolarPost(request.forms, request.files, author=member, thumbnail=(200, 150))
 81  
 82      # Storing all posts in one directory may cause collisions,
 83      # but that can be resolved by making them user-specific and
 84      # storing in subdirectories
 85      post.store(f'credenso/menu')
 86  
 87      return redirect(request.environ.get('HTTP_REFERER'))
 88  
 89  @app.post('/<month>/order')
 90  def order_post(month):
 91      honey = request.forms.get('honey')
 92      if honey != '':
 93          return redirect(request.environ.get('HTTP_REFERER'))
 94  
 95      data = {
 96          'name': request.forms.get('name'),
 97          'contact': request.forms.get('contact'),
 98          'info': request.forms.get('info')
 99      }
100  
101      writable_month = slugify(month)
102  
103      save(data, 'subscriptions/' + writable_month, name=data.get('name'))
104      # Implement this soonish
105      #msg = Message(request.forms, to="zen", from="credenso")
106      #msg.send()
107  
108      return redirect(f'{path}thanks.html')
109  
110  @app.post('/finances')
111  def finances_post():
112      session = load_session()
113      try:
114          member = session.get('member')
115      except AttributeError:
116          # If getting the member fails, redirect to login
117          return redirect('/login')
118  
119      
120      date = request.forms.get("date")
121      description = request.forms.get("description")
122      amount = int(request.forms.get("amount"))
123      amount = "{:0.2f}".format(abs(amount))
124  
125      entry = f'{date},{description},{amount}'
126  
127      link = request.forms.get("link")
128      
129      if link is not None:
130          entry += ',' + link
131  
132      with open(f'{here}/credenso_budget.csv', 'a', newline='') as csvfile:
133          csvfile.write(entry)
134  
135      # Storing all posts in one directory may cause collisions,
136      # but that can be resolved by making them user-specific and
137      # storing in subdirectories
138  
139      return redirect(request.environ.get('HTTP_REFERER'))
140  
141  app.post('/login', callback=login)
142  
143  # When deployed, this path is managed by NGINX
144  @app.route('/static/<filepath:path>')
145  def static(filepath):
146          return static_file(filepath, root=f'{here}/static')
147  
148  ##################################################
149  ### Frontend Routes ##############################
150  ##################################################
151  
152  # These are the routes that expect to render content for the client
153  
154  @app.route('/finances.html')
155  def finances():
156      budget_info = defaultdict(list)
157      totals = defaultdict(int)
158      ledger = []
159  
160      with open(f'{here}/credenso_budget.csv', newline='') as csvfile:
161              budget = csv.DictReader(csvfile)
162              for row in budget:
163                  # parse the date
164                  date = datetime.strptime(row['Date'], '%Y-%m-%d')
165                  year = date.strftime('%Y')
166                  day = date.strftime('%b %d, %Y')
167                  description = row['Description']
168                  link = row['Link']
169  
170                  amount = float(row['Amount'])
171  
172                  totals[year] += amount
173  
174                  negative = amount < 0
175  
176                  budget_info[year].append({
177                      'date': date,
178                      'day': day,
179                      'description': description,
180                      'amount': "{:0.2f}".format(abs(amount)),
181                      'negative': negative,
182                      'link': link
183                  })
184  
185              for year in budget_info:
186                  ledger.append({'year': year, 'entries': reversed(budget_info[year]), 'total': "{:0.2f}".format(abs(totals[year])), 'neg': totals[year] < 0 })
187  
188      data = {
189              'name': 'Credenso',
190              'label': 'Finances',
191              'ledger': reversed(ledger)
192      }
193      return chevron_template(f'credenso/finances.html', **defaults(data));
194  
195  @app.route('/admin.html')
196  def admin():
197      session = load_session()
198      data = {
199              'session': session,
200              'name': 'Credenso',
201              'label': 'Admin'
202      }
203      return chevron_template(f'credenso/admin.html', **defaults(data));
204  
205  @app.route('/cafe.html')
206  def cafe():
207      posts = load_posts('credenso/menu')
208      food = []
209      drink = []
210      etc = []
211      for post in posts:
212          category = post.get('metadata').get('category')
213          if category == "food":
214              food.append(post)
215          elif category == "drink":
216              drink.append(post)
217          elif category == "other":
218              etc.append(post)
219  
220      data = {
221              'name': 'Credenso',
222              'label': 'Café',
223              'menu': posts,
224              'food_menu': food,
225              'drink_menu': drink,
226              'etc_menu': etc
227      }
228      return chevron_template(f'credenso/cafe.html', **defaults(data))
229  
230  @app.route('/orders.html')
231  def orders():
232      now = datetime.today()
233      next_month = (now.replace(day=1) + timedelta(days=32))
234      data = {
235              'name': 'Credenso',
236              'next_month': next_month.strftime('%B %Y')
237      }
238      return chevron_template(f'credenso/orders.html', **defaults(data))
239  
240  @app.route('/<template>.html')
241  def index(template):
242      data = {
243              'name': 'Credenso'
244      }
245      return chevron_template(f'credenso/{template}.html', **defaults(data))
246  
247  @app.route('/')
248  def index():
249      data = {
250              'name': 'Solar',
251              'main': True
252      }
253      return chevron_template('credenso/index.html', **defaults(data))
254  
255  
256  ##################################################
257  ### Running the code #############################
258  ##################################################
259  
260  # Normally, a planet will be pushed into orbit by the main app within the Solar system.
261  # However, any app can serve as the main landing page if given access to elements
262  
263  if __name__ == "__main__":
264      app.run(host='0.0.0.0', port=1618, debug=True)