Browse Source

first commit

master
Colat 11 months ago
commit
908872467b
  1. 135
      .gitignore
  2. 41
      README.md
  3. 31
      banner.txt
  4. 14
      config-example.py
  5. 37
      ganciointegration.py
  6. 97
      models.py
  7. 133
      parser.py
  8. 79
      run.py
  9. 2
      setup.py
  10. 59
      updater.py

135
.gitignore vendored

@ -0,0 +1,135 @@
usurpa.pdf
# Configuration file, check config-example.py
config.py
# For pycharm ide
.idea
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
### Python Patch ###
.venv/
# End of https://www.gitignore.io/api/python

41
README.md

@ -0,0 +1,41 @@
MMMMMMMMMMMMMMMMMMMMMMMMMWN0dc,.. ..,cx0NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMWNOdc'. .,cdONMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMXkc' .'ckXMMMMMMMMMMMMWNXK0OkdoloO
MMMMMMMMMMMMMMMMW0l' 'l0WMMMWKdlc:,'... cX
MMMMMMMMMMMMMMWO:. .,:ldxkOOOkkxdoc;.. .c0WMMXl. cXM
MMMMMMMMMMMMMKc. .;oOXWMMMMMMMMMMMMMMWN0dc. .lKNOc. cXMM
MMMMMMMMMMMWk' ,dKWMMMMMMMMMMMMMMMMMMMMMMWXk:. .' cXMMM
MMMMMMMMMMNo. ,kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMW0c. cXMMMM
MMMMMMMMMNl. .dNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWx. .;dx;.cXMMMMM
MMMMMMMMWd. ,OWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMW0o, .l0WMMN0XMMMMMM
MMMMMMMMO. ,0MMMMMMMMMMMMMMMMWXXWMMMMMMMMMMWXx;. 'OMMMMMMMMMMMMM
MMMMMMMNc .OMMMMMMMMMMMMMMWXx:..lKWMMMMMMNkc. lNMMMMMMMMMMMM
MMMMMMMO. oWMMMMMMMMMMMMNOc. .oOXWW0o, ';. '0MMMMMMMMMMMM
MMMMMMWd. .OMMMMMMMMMMN0o' 'c;. .:kXX: .xMMMMMMMMMMMM
MMMMMMWo ,KMMMMMMMWKd;. ,oKWMMWl dWMMMMMMMMMMM
MMMMMMWo ,KMMMMWXx:. .cONMMMMMWl dWMMMMMMMMMMM
MMMMMMWd. '0MMNOc. .cxx; .;xXWMMMMMMMNc .xMMMMMMMMMMMM
MMMMMMMO. .o0o, .:xXWMMNx, 'o0WMMMMMMMMMM0' '0MMMMMMMMMMMM
MMMMMMMNc .. .;dKWMMMMMMMXd:;ckNMMMMMMMMMMMMNl lNMMMMMMMMMMMM
MMMMMMMMO. .,d0WMMMMMMMMMMMMWWWMMMMMMMMMMMMMWx. '0MMMMMMMMMMMMM
MMMMMMMNx' ,o0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWx. .xWMMMMMMMMMMMMM
MMMMW0o, ,kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXl. .dNMMMMMMMMMMMMMM
MWKd;. .lKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNx' .dNMMMMMMMMMMMMMMM
k:. .. .cONMMMMMMMMMMMMMMMMMMMMMMMMWKd, ,OWMMMMMMMMMMMMMMMM
; .:xKKl. .,oOXWMMMMMMMMMMMMMMMMMN0x:. .oXMMMMMMMMMMMMMMMMMM
Xo. .;dKWMMMW0c. .;ldO0KXNNNXXK0Oxo:'. .c0WMMMMMMMMMMMMMMMMMMM
MWO:;o0WMMMMMMMMW0o' .......... 'oKWMMMMMMMMMMMMMMMMMMMMM
MMMWWMMMMMMMMMMMMMMNkl'. .,lkNMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMN0dc,. ..,cd0NMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMWN0xl;.. ..;lx0NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
Welcome to usurpa-api !
# Usurpa-api
This api is based on the popular, ancient and usable squat schedule:
https://usurpa.squat.net
The objective of the api is to modernize the work of usurpa mates to be more adapted to our times.

31
banner.txt

@ -0,0 +1,31 @@
MMMMMMMMMMMMMMMMMMMMMMMMMWN0dc,.. ..,cx0NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMWNOdc'. .,cdONMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMXkc' .'ckXMMMMMMMMMMMMWNXK0OkdoloO
MMMMMMMMMMMMMMMMW0l' 'l0WMMMWKdlc:,'... cX
MMMMMMMMMMMMMMWO:. .,:ldxkOOOkkxdoc;.. .c0WMMXl. cXM
MMMMMMMMMMMMMKc. .;oOXWMMMMMMMMMMMMMMWN0dc. .lKNOc. cXMM
MMMMMMMMMMMWk' ,dKWMMMMMMMMMMMMMMMMMMMMMMWXk:. .' cXMMM
MMMMMMMMMMNo. ,kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMW0c. cXMMMM
MMMMMMMMMNl. .dNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWx. .;dx;.cXMMMMM
MMMMMMMMWd. ,OWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMW0o, .l0WMMN0XMMMMMM
MMMMMMMMO. ,0MMMMMMMMMMMMMMMMWXXWMMMMMMMMMMWXx;. 'OMMMMMMMMMMMMM
MMMMMMMNc .OMMMMMMMMMMMMMMWXx:..lKWMMMMMMNkc. lNMMMMMMMMMMMM
MMMMMMMO. oWMMMMMMMMMMMMNOc. .oOXWW0o, ';. '0MMMMMMMMMMMM
MMMMMMWd. .OMMMMMMMMMMN0o' 'c;. .:kXX: .xMMMMMMMMMMMM
MMMMMMWo ,KMMMMMMMWKd;. ,oKWMMWl dWMMMMMMMMMMM
MMMMMMWo ,KMMMMWXx:. .cONMMMMMWl dWMMMMMMMMMMM
MMMMMMWd. '0MMNOc. .cxx; .;xXWMMMMMMMNc .xMMMMMMMMMMMM
MMMMMMMO. .o0o, .:xXWMMNx, 'o0WMMMMMMMMMM0' '0MMMMMMMMMMMM
MMMMMMMNc .. .;dKWMMMMMMMXd:;ckNMMMMMMMMMMMMNl lNMMMMMMMMMMMM
MMMMMMMMO. .,d0WMMMMMMMMMMMMWWWMMMMMMMMMMMMMWx. '0MMMMMMMMMMMMM
MMMMMMMNx' ,o0WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWx. .xWMMMMMMMMMMMMM
MMMMW0o, ,kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXl. .dNMMMMMMMMMMMMMM
MWKd;. .lKWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNx' .dNMMMMMMMMMMMMMMM
k:. .. .cONMMMMMMMMMMMMMMMMMMMMMMMMWKd, ,OWMMMMMMMMMMMMMMMM
; .:xKKl. .,oOXWMMMMMMMMMMMMMMMMMN0x:. .oXMMMMMMMMMMMMMMMMMM
Xo. .;dKWMMMW0c. .;ldO0KXNNNXXK0Oxo:'. .c0WMMMMMMMMMMMMMMMMMMM
MWO:;o0WMMMMMMMMW0o' .......... 'oKWMMMMMMMMMMMMMMMMMMMMM
MMMWWMMMMMMMMMMMMMMNkl'. .,lkNMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMN0dc,. ..,cd0NMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMWN0xl;.. ..;lx0NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM

14
config-example.py

@ -0,0 +1,14 @@
#!/usr/bin/python
# Name of the application
APP_BASE_NAME = 'usurpa-api'
# File to update
USURPA_URL = 'https://usurpa.squat.net/usurpa.pdf'
USURPA_DOWNLOAD_PATH = 'usurpa-proves1.ods'
# Gancio
GANCIO_URL = "http://localhost:13120"
# TODO: Seguro que hay una forma mas segura de hacer esto, pero lo mirare mas adelante
GUSER = "example@example.es"
GPASSWORD = "examplepassword"

37
ganciointegration.py

@ -0,0 +1,37 @@
#!/usr/bin/env python3
from models import GancioEvent
import requests
import config
# Hace una petición POST a /api/event con los datos de evento
def CreateEvent(sendData,token):
try:
headers = {'Authorization': 'Bearer ' + token }
url = config.GANCIO_URL + "/api/event"
resp = requests.post(url,files = sendData,headers = headers)
if resp.status_code == 200:
print("Se ha creado el evento con exito")
except Exception as e:
print("Error doing the request to the api:")
print(str(e))
def GetLoginToken():
url = config.GANCIO_URL
payload = {"username" : config.GUSER , "password" : config.GPASSWORD , "grant_type": "password" , "client_id": "self"}
resp = requests.post(url,data= payload)
return resp.json()["access_token"]
def UploadEventGancio(Event):
try:
event = GancioEvent(Event["title"],Event["description"],Event["squat"],Event["street"],Event["start_datetime"],Event["end_datetime"],"False")
token = GetLoginToken()
sendData = event.getMultipartFormData()
CreateEvent(sendData,token)
except Exception as e:
print("Error creating the event in gancio")
print(str(e))
return
#GancioEvent = {"title": "" , "description", "placename": "", "placeaddress": "", "startdatetime", "", "end_datetime": "" }

97
models.py

@ -0,0 +1,97 @@
import config
# squatNameExceptions = config.NAME_EXCEPTIONS
# zones = config.REGIONAL_ZONES
class UsurpaSection(object):
def __init__(self, title):
self.title = title
self.rows = []
def addRow(self, row):
self.rows.append(row)
class Squat(object):
def __init__(self, row):
self.row = row
# The ezodf cell object should have 3 lines corresponding to
# 1. Squat name
# 2. Address
# 3. Metro station
cellSplit = row[0].value.split("\n")
self.name = cellSplit[0] if 0 < len(cellSplit) else None
self.address = cellSplit[1] if 1 < len(cellSplit) else None
self.metro = cellSplit[2] if 2 < len(cellSplit) else None
# Check if has extra information (a row split)
self.hasRowSpan = True if row[0].span[0] > 1 else False
self.rowSpan = row[0].span[0]
self.additionalInfo = []
# Initialize th 7 days of the week
self.events = [None] * 7
class RegionalZone (object):
def __init__(self, name=None):
self.name = name
self.rows = []
self.squatList = []
def addRow(self, row):
"""
Add a row object [] into the sets of self.rows[]
:param row:
:return:
"""
self.rows.append(row)
# TODO: DRY this function is basically the same as parser.parseCSV
def generateSquats(self):
squat = None
for row in self.rows:
squatName = Squat.isSquatName(row[0])
if squatName:
squat = Squat(squatName)
self.squatList.append(squat)
if squat is not None:
squat.addRow(row)
# TODO: Try to do this without making loop
# Check if a row contains a zone name defined on config file
@classmethod
def checkIfIsZone(cls, row):
for zone in zones:
if zone in row:
return zone
return False
class GancioEvent:
def __init__(self,title,description,place_name,place_address,start_datetime,end_datetime):
self.title = str(title)
self.description = str(description)
self.place_name = str(place_name)
self.place_address = str(place_address)
self.start_datetime = int(start_datetime)
self.end_datetime = int(end_datetime)
self.multidate = "False"
def getJson(self):
EventDict = {}
EventDict["title"] = self.title
EventDict["description"] = self.description
EventDict["place_name"] = self.place_name
EventDict["place_address"] = self.place_address
EventDict["start_datetime"] = self.start_datetime
EventDict["multidate"] = self.multidate
return EventDict
def getMultipartFormData(self):
body = {}
for atribute in self.__dict__.keys():
body[atribute] = (None, self.__dict__[atribute])
return body

133
parser.py

@ -0,0 +1,133 @@
import sys
import config, ezodf
from models import RegionalZone, UsurpaSection, Squat
import lxml.etree as etree
from ezodf.styles import OfficeAutomaticStyles
CELL_WIDTH=8 # number of cells to iterate when analize a row
appName = config.APP_BASE_NAME
usurpaOds = config.USURPA_DOWNLOAD_PATH
sectionList = {}
squatList = {}
days = [] # Used to store the days
class AutomaticStyles:
_TAG = "{urn:oasis:names:tc:opendocument:xmlns:office:1.0}automatic-styles"
_PROP_PREFIX='{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
TABLE_CELL_PROPERTIES = _PROP_PREFIX+"table-cell-properties"
PARAGRAPH_PROPERTIES = _PROP_PREFIX+"paragraph-properties"
TEXT_PROPERTIES = _PROP_PREFIX+"text-properties style"
def __init__(self, xmlnode):
xmlstyles = next((child for child in xmlnode if child.tag == self._TAG), None)
self.automaticStyles = OfficeAutomaticStyles(xmlstyles)
def getStyleProperties(self, styleName, prop):
xmlstyle = self.automaticStyles._find(styleName)
if xmlstyle is None: return None
return next((child for child in xmlstyle if child.tag == prop ), None)
def getBackgroundColor(self, styleName):
tag = '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}background-color'
cellStyle = self.getStyleProperties(styleName, AutomaticStyles.TABLE_CELL_PROPERTIES)
return cellStyle.get(tag) if cellStyle is not None else None
def parse(input=usurpaOds):
print("# Parsig", input)
doc = ezodf.opendoc(input)
if doc.doctype == 'odt':
# Get styles
print("* Parsing styles")
styles = AutomaticStyles(doc.filemanager.get_xml_element('content.xml'))
print("* Parsing sections")
# sheet = doc.sheets[0]
# sheet = doc.body.filter(kind='Table')
# print(doc.body.__dict__)
sheet = None
for obj in doc.body:
if type(obj) == ezodf.table.Table:
sheet = obj
break
section = None
for rindex, row in enumerate(sheet.rows()):
stylename = row[0].style_name
bgColor = styles.getBackgroundColor(stylename)
print(row[0].value)
# If bgColor is black mark the beginning of a new UsurpaSection
if bgColor == "#000000":
# Store old section
if section is not None: sectionList[section.title] = section
section = UsurpaSection(row[0].value)
actualSection = len(sectionList)
# Now lets analize the entire row
if section is not None:
# todo: First section. Demonstrations, info usurpa number and week
if actualSection == 0:
pass
# todo: "Jornades i actes ocasionals"
elif actualSection == 1:
# The first cell is the title and the second is spanned until the end with the event info
pass
# Days and their position
elif actualSection == 2:
for i in range(1, CELL_WIDTH):
days.append(row[i].value)
# Squats
elif actualSection > 2:
# If is not the header of the section
if bgColor != "#000000":
# First create the Squat object... Oh yeah
if row[0].value is None: continue
squat = Squat(row)
# Then parse the day events
for i in range(1, CELL_WIDTH):
# todo: fix the template because there are two spanned cells that are not visible
print(days)
day = i-1 # The day of the week (this loop starts at 1 and the array of days inside squat at 0)
print(days[day])
print(row[i].value)
print(row[i].span)
if squat.events[day] is None:
# If the cell is spanned to more than one days copy the event on this days
for span in range(0, row[i].span[1]): squat.events[day + span] = row[i].value
# Check if has aditional info
if squat.hasRowSpan:
for i in range(1, squat.rowSpan):
squat.additionalInfo.append(sheet.row(rindex+1)[1].value)
squatList[squat.name] = squat
section.addRow(row)
print("* Squats found:", len(squatList))
# for k,v in sectionList.items():
# print(k)
else:
print("Bad format for", input)
raise
d = 2
s = "AT. LLIB. GRÀCIA"
print(squatList[s].name, ": ", days[d], squatList[s].events[d])
print("INFO adicional: "+"".join(squatList[s].additionalInfo if squatList[s].hasRowSpan else "No te informacio adicional"))
d=3
s="HORT VALLCARCA"
print(squatList[s].name, ": ", days[d], squatList[s].events[d])
print("INFO adicional: "+"".join(squatList[s].additionalInfo if squatList[s].hasRowSpan else "No te informacio adicional"))

79
run.py

@ -0,0 +1,79 @@
import sys, getopt, os, time
import updater, config, parser
appName = config.APP_BASE_NAME
def fileExists(file):
return os.path.isfile(file)
def updateUsurpa(output):
if output is None:
updater.updateUsurpa()
else:
updater.updateUsurpa(output)
def parseUsurpa(input=None):
if input is None : input = config.USURPA_DOWNLOAD_PATH
if fileExists(input):
parser.parse(input=input)
else:
print ("Error: can't find", input)
# Main
def main(argv):
try:
def printBanner():
f = open('banner.txt', 'r')
file_contents = f.read()
print( file_contents, end="", flush=True)
print ("Welcome to ", appName, "!\n", end="", flush=True)
def printHelp ():
print("Help me to squat!!")
print("\nExample:\n\tpython run.py -u -p\n\nOptions:")
print("\t-h Print this help")
print("\t-i Specify input file")
print("\t-o Specify output file")
print("\t-u Update", config.USURPA_DOWNLOAD_PATH, "from", config.USURPA_URL,
"\n\t Specify output with -o")
print("\t-p Parse", config.USURPA_DOWNLOAD_PATH,". Specify -i to specify the input file")
print("\t-❤ Retrieve list of all empty houses to squat")
printBanner()
if argv.__len__() == 0:
printHelp()
try:
opts, args = getopt.getopt(argv, "hucpi:o:", "parse=") # Letters recognized. If a letter has argument follow it with a colon :, like p: fill be '-p arg'
except getopt.GetoptError:
printHelp()
sys.exit(2)
input = None
output = None
for opt, arg in opts:
if opt == '-h':
printHelp()
# sys.exit(0)
break
elif opt in ("-i"):
input = arg
elif opt in ("-o"):
output = arg
elif opt in ("-u"):
updateUsurpa(output)
elif opt in ("-p"):
parseUsurpa(input=input)
# elif opt in ("--parse"):
# file = None
# if arg != "":
# file=arg
# parseUsurpa(file=file)
elif opt in ("-❤"):
printHelp()
except:
raise BaseException("Error: failed to execute the program")
if __name__ == '__main__':
main(sys.argv[1:])

2
setup.py

@ -0,0 +1,2 @@
#!/usr/bin/python

59
updater.py

@ -0,0 +1,59 @@
import sys, getopt, os, time
from datetime import datetime
import config
import urllib.request
appName = config.APP_BASE_NAME
usurpaURL = config.USURPA_URL
usurpaDownloadPath = config.USURPA_DOWNLOAD_PATH
def isUsurpadownloaded():
return os.path.isfile(usurpaDownloadPath)
def getConnection(url = usurpaURL):
return urllib.request.urlopen(url, timeout=30)
# Get last modified attribure of remote url
def getRemoteLastModified(conn):
return datetime.strptime(conn.headers['last-modified'], '%a, %d %b %Y %H:%M:%S GMT')
# Get last modified of local file
def getLocalLastModified(file):
if not os.path.isfile(file):
return False
stat = os.stat(file)
try:
return datetime.fromtimestamp(stat.st_birthtime)
except AttributeError:
# We're probably on Linux. No easy way to get creation dates here,
# so we'll settle for when its content was last modified.
return datetime.fromtimestamp(stat.st_mtime)
def downloadUsurpa(url=usurpaURL, downloadPath = usurpaDownloadPath):
print ("- Start download of ", usurpaURL )
# Download the file
urllib.request.urlretrieve(url, downloadPath)
# Set modification date
t = time.mktime(getRemoteLastModified(getConnection()).timetuple())
os.utime(usurpaDownloadPath, (t, t))
def isUsurpaUpdated(remote, local):
if remote == local:
return True
return False
def updateUsurpa(output=usurpaDownloadPath):
print("# ", appName,"check for updates ", )
try:
if not isUsurpadownloaded:
print("- No ", appName," file found, proceed to download on\n", output)
downloadUsurpa(usurpaURL, downloadPath=output)
elif not isUsurpaUpdated(getRemoteLastModified(getConnection()), getLocalLastModified(output)):
print("- ", appName," is not updated, proceed to update: \n", output)
downloadUsurpa(usurpaURL, downloadPath=output)
print(appName, "updated succesfully")
print("Done.")
else:
print(appName, "was already updated")
except:
raise Exception("Error: Failed to update usurpa")
Loading…
Cancel
Save