From aa1726ca248d2a227979f57aaf5123684fa855de Mon Sep 17 00:00:00 2001 From: Fabian Gallenkamp <fabian.gallenkamp@uni-hamburg.de> Date: Fri, 1 Mar 2019 16:12:11 +0100 Subject: [PATCH] prettify export mechanism --- app.py | 305 ++++++++++++++++++++++++++++------------------- sample_db.sqlite | Bin 90112 -> 94208 bytes 2 files changed, 184 insertions(+), 121 deletions(-) diff --git a/app.py b/app.py index f49b10d..d69cfcb 100644 --- a/app.py +++ b/app.py @@ -174,6 +174,66 @@ class Reference(db.Model): #@app.route('/') #def index(): # return redirect(url_for("admin.index")) +@app.route('/wiki-export') +def index(): + # Generate sub pages for tools + with open('templates/export/software.jinja2', "r", encoding="utf-8") as file_: + template = Template(file_.read()) + softwares = Software.query.all() + for software_tool in softwares: + template.stream(software=software_tool).dump( + '../digitale-Methoden-wiki/Tool_' + software_tool.name.replace(' ', '') + '.asciidoc', encoding='utf-8') + + softwareincategory = [] + software_categorys = SoftwareCategory.query.all() + for software_category in software_categorys: + softwares = Software.query.filter(Software.softwarecategory_id == software_category.id) + softwareincategory.append((software_category, softwares)) + + # Generate tools overview page + with open('templates/export/softwares.jinja2', "r", encoding="utf-8") as file_: + template = Template(file_.read()) + template.stream(softwareincategory=softwareincategory).dump('../digitale-Methoden-wiki/SoftwareToolsList.asciidoc', encoding='utf-8') + + # Generate methods overview page + hierarchy = db.session.query(Method, literal(0).label('level')).filter(Method.parent_id == null()) \ + .cte(name="hierarchy", recursive=True) + + parent = aliased(hierarchy, name="p") + children = aliased(Method, name="c") + hierarchy = hierarchy.union_all( + db.session.query( + children, + (parent.c.level + 1).label("level")) + .filter(children.parent_id == parent.c.id)) + + result = db.session.query(hierarchy.c.level, hierarchy.c.id, hierarchy.c.parent_id, hierarchy.c.name, + hierarchy.c.description) \ + .select_entity_from(hierarchy).order_by(hierarchy.c.id) \ + .all() + references = db.session.query(Reference).order_by(Reference.name).all() + + # Generate sub pages + with open('templates/export/MethodsList.jinja2', "r", encoding="utf-8") as file_: + template = Template(file_.read()) + template.stream(methods=result, references=references).dump('../digitale-Methoden-wiki/MethodsList.asciidoc', + encoding='utf-8') + + base_path = pathlib.Path('../digitale-Methoden-wiki/') + #data = io.BytesIO() + #with zipfile.ZipFile(data, mode='w') as z: + # for f_name in base_path.iterdir(): + # z.write(f_name) + #data.seek(0) + + #return send_file( + # data, + # mimetype='application/zip', + # as_attachment=True, + # attachment_filename='data.zip' + #) + flash("Wiki-pages exported to " + str(base_path)) + return redirect(url_for("admin.index")) class AdvancedSoftwareView(sqla.ModelView): @@ -196,74 +256,6 @@ class AdvancedSoftwareView(sqla.ModelView): 'links': _links_formatter } - @action('advancedexport', 'AdvancedExport') - def action_advancedexport(self, ids): - try: - # Generate sub pages - with open('templates/export/software.jinja2', "r", encoding="utf-8") as file_: - template = Template(file_.read()) - softwares = Software.query.filter(Software.id.in_(ids)) - for software_tool in softwares: - template.stream(software=software_tool).dump('../digitale-Methoden-wiki/Tool_' + software_tool.name.replace(' ','') + '.asciidoc', encoding='utf-8') - - softwareincategory = [] - software_categorys = SoftwareCategory.query.all() - for software_category in software_categorys: - softwares = Software.query.filter(Software.softwarecategory_id == software_category.id) - softwareincategory.append((software_category,softwares)) - - # Generate overview page - with open('templates/export/softwares.jinja2', "r", encoding="utf-8") as file_: - template = Template(file_.read()) - template.stream(softwareincategory=softwareincategory).dump('../digitale-Methoden-wiki/SoftwareToolsList.asciidoc', encoding='utf-8') - - base_path = pathlib.Path('../digitale-Methoden-wiki/') - data = io.BytesIO() - with zipfile.ZipFile(data, mode='w') as z: - for f_name in base_path.iterdir(): - z.write(f_name) - data.seek(0) - return send_file( - data, - mimetype='application/zip', - as_attachment=True, - attachment_filename='data.zip' - ) - flash("Files generated!") - except Exception as ex: - if not self.handle_view_exception(ex): - raise - flash("Not done") - - @action('advancedexport2', 'AdvancedExport2') - def action_advancedexport2(self, ids): - try: - hierarchy = db.session.query(Method, literal(0).label('level')).filter(Method.parent_id == null()) \ - .cte(name="hierarchy", recursive=True) - - parent = aliased(hierarchy, name="p") - children = aliased(Method, name="c") - hierarchy = hierarchy.union_all( - db.session.query( - children, - (parent.c.level + 1).label("level")) - .filter(children.parent_id == parent.c.id)) - - result = db.session.query(hierarchy.c.level, hierarchy.c.id, hierarchy.c.parent_id, hierarchy.c.name, hierarchy.c.description)\ - .select_entity_from(hierarchy).order_by(hierarchy.c.id)\ - .all() - references = db.session.query(Reference).order_by(Reference.name).all() - - # Generate sub pages - with open('templates/export/MethodsList.jinja2', "r", encoding="utf-8") as file_: - template = Template(file_.read()) - template.stream(methods=result, references=references).dump('../digitale-Methoden-wiki/MethodsList.asciidoc', encoding='utf-8') - flash("Files generated!") - except Exception as ex: - if not self.handle_view_exception(ex): - raise - flash("Not done") - # Create admin admin = admin.Admin(app, name='digital methods:software-tools', template_mode='bootstrap3', index_view=AdminIndexView( name='home', @@ -274,13 +266,14 @@ admin = admin.Admin(app, name='digital methods:software-tools', template_mode='b # Add views admin.add_view(AdvancedSoftwareView(Software, db.session, name="software-tools")) admin.add_view(sqla.ModelView(Method, db.session, name="methods")) -admin.add_view(sqla.ModelView(Feature, db.session, category="miscellaneous")) -admin.add_view(sqla.ModelView(License, db.session, category="miscellaneous")) -admin.add_view(sqla.ModelView(Link, db.session, category="miscellaneous")) -admin.add_view(sqla.ModelView(SoftwareCategory, db.session, category="miscellaneous")) +admin.add_view(sqla.ModelView(Feature, db.session, name="software-features", category="miscellaneous")) +admin.add_view(sqla.ModelView(License, db.session, name="licenses", category="miscellaneous")) +admin.add_view(sqla.ModelView(Link, db.session, name="links", category="miscellaneous")) +admin.add_view(sqla.ModelView(SoftwareCategory, db.session, name="software categories", category="miscellaneous")) admin.add_sub_category(name="other collections", parent_name="miscellaneous") admin.add_link(MenuLink(name="CRAN-R", url='https://cran.r-project.org/web/views/', category='other collections', target="_blank")) admin.add_link(MenuLink(name="ROpenSci", url='https://ropensci.org/packages/', category='other collections', target="_blank")) +admin.add_link(MenuLink(name="wiki-export", url='/wiki-export', category="miscellaneous")) @@ -415,115 +408,167 @@ Furthermore the server-client-model is the established communication paradigms f parent=method3) db.session.add(method3) method2 = Method(id=25,name="information retrieval", - description="", - parent=method1) - db.session.add(method2) - method2 = Method(id=26,name="indexing", - description="", - parent=method1) - db.session.add(method2) - method2 = Method(id=27,name="searching", - description="", + description="Retrieve relevant informations in response to the information requests.", parent=method1) db.session.add(method2) + method3 = Method(id=26,name="indexing", + description="'organize data in such a way that it can be easily retrieved later on'(<<Ignatow_etal2017>>,137)", + parent=method2) + db.session.add(method3) + method3 = Method(id=27,name="searching/querying", + description="'take information requests in the form of queries and return relevant documents'(<<Ignatow_etal2017>>,137). There are different models in order to estimate the similarity between records and the search queries (e.g. boolean, vector space or a probabilistic model)(ibid).", + parent=method2) + db.session.add(method3) method2 = Method(id=28,name="statistical analysis", description="", parent=method1) db.session.add(method2) method3 = Method(id=29,name="frequency analysis", - description="Descriptiv statistical analysis by using text fragments", + description="Descriptiv statistical analysis by using specific text abundances.", parent=method2) db.session.add(method3) - method3 = Method(id=30,name="classification/machine learning", - description="", - parent=method2) - db.session.add(method3) - method4 = Method(id=31,name="supervised classification", - description="", + method4 = Method(id=30,name="word frequencies/dictionary analysis", + description="Analyse statistical significant occurence of words/word-groups. Can also be combined with meta-data (e.g. creation time of document).", parent=method3) db.session.add(method4) - method4 = Method(id=32,name="topic modelling", - description="", + method4 = Method(id=31,name="co-occurence analysis", + description="Analyse statistical significant co-occurence of words in different contextual units.", parent=method3) db.session.add(method4) - method4 = Method(id=33,name="latent dirichlet allocation", - description="", + method3 = Method(id=32,name="classification/machine learning", + description="Various techniques to (semi-)automatically identify specific classes. ", + parent=method2) + db.session.add(method3) + method4 = Method(id=33,name="supervised classification", + description="Use given training examples in order to classify certain entities.", parent=method3) db.session.add(method4) - method4 = Method(id=34,name="structural topic modelling", - description="", + method4 = Method(id=34,name="latent semantic analysis", + description="'The basic idea of latent semantic analysis (LSA) is, that text do have a higher order (=latent semantic) structure which, however, is obscured by word usage (e.g. through the use of synonyms or polysemy). By using conceptual indices that are derived statistically via a truncated singular value decomposition (a two-mode factor analysis) over a given document-term matrix, this variability problem can be overcome.'(link:https://cran.r-project.org/web/packages/lsa/lsa.pdf[CRAN-R])", parent=method3) db.session.add(method4) - method4 = Method(id=35,name="latent semantic analysis", - description="", + method4 = Method(id=35,name="topic modelling", + description="Probabilistic models to infer semantic clusters. See especially <<Papilloud_etal2018>>.", parent=method3) db.session.add(method4) - method4 = Method(id=36,name="sentiment analysis", + method5 = Method(id=36,name="latent dirichlet allocation", + description="""'The application of LDA is based on three nested concepts: the text collection to be modelled is referred to as the corpus; one item within the corpus is a document, with words within a document called terms.(...) + +The aim of the LDA algorithm is to model a comprehensive representation of the corpus by inferring latent content variables, called topics. Regarding the level of analysis, topics are heuristically located on an intermediate level between the corpus and the documents and can be imagined as content-related categories, or clusters. (...) Since topics are hidden in the first place, no information about them is directly observable in the data. The LDA algorithm solves this problem by inferring topics from recurring patterns of word occurrence in documents.'(<<Maier_etal2018>>,94)""", + parent=method4) + db.session.add(method5) + method5 = Method(id=37,name="non-negative-matrix-factorization", + description="Inclusion of non-negative constraint.", + parent=method4) + db.session.add(method5) + method5 = Method(id=38,name="structural topic modelling", + description="Inclusion of meta-data. Refer especially to <<roberts2013>>.", + parent=method4) + db.session.add(method5) + method4 = Method(id=39,name="sentiment analysis", description="'Subjectivity and sentiment analysis focuses on the automatic identification of private states, such as opinions, emotions, sentiments, evaluations, beliefs, and speculations in natural language. While subjectivity classification labels text as either subjective or objective, sentiment classification adds an additional level of granularity, by further classifying subjective text as either positive, negative, or neutral.' (<<Ignatow_etal2017>> pp. 148)", parent=method3) db.session.add(method4) - method4 = Method(id=37,name="automated narrative, argumentative structures, irony, metaphor detection/extraction", - description="", + method4 = Method(id=40,name="automated narrative, argumentative structures, irony, metaphor detection/extraction", + description="For automated narrative methapor analysis see (<<Ignatow_etal2017>>, 89-106. For argumentative structures(Task: Retrieving sentential arguments for any given controversial topic) <<Stab_etal2018>> .Refer for a current overview <<Cabrio2018>>.", parent=method3) db.session.add(method4) - method3 = Method(id=38,name="network analysis", - description="", + method3 = Method(id=41,name="network analysis/modelling", + description="Generate networks out of text/relationships between text.", parent=method2) db.session.add(method3) - method4 = Method(id=39, name="knowledge graph construction", - description="finding factual information in free text", + method4 = Method(id=42, name="knowledge graph construction", + description="Modelling entities and their relationships.", parent=method3) db.session.add(method4) - method2 = Method(id=40,name="data visualization", - description="", + method2 = Method(id=43,name="data visualization", + description="Visualize the mined informations.", parent=method1) db.session.add(method2) - method3 = Method(id=41,name="dynamic visualizations", + method3 = Method(id=44,name="word relationships", description="", parent=method2) db.session.add(method3) - - method1 = Method(id=42,name="science practice", + method3 = Method(id=45,name="networks", + description="", + parent=method2) + db.session.add(method3) + method3 = Method(id=46,name="geo-referenced", description="", + parent=method2) + db.session.add(method3) + method3 = Method(id=47,name="dynamic visualizations", + description="Visualizations with user interaction or time frames.", + parent=method2) + db.session.add(method3) + method1 = Method(id=48,name="science practice", + description="General science practice", parent=method) db.session.add(method1) - method2 = Method(id=43,name="digital research design", - description="", + method2 = Method(id=49,name="digital research design", + description="New possibilities in surveys or data aquisition techniques.", parent=method1) db.session.add(method2) - method2 = Method(id=44,name="collaborative work", + method3 = Method(id=50,name="ecological momentary assessments (EMA)/Experience Sampling Method (ESM)", + description="Mostly equivalent. EMA focusses on medical questions or measurements in a natural environment; ESM more on subjective Questions in the real life. Four characteristics: 1) data collection in natural environments 2) Focussing on near events/impressions/actions 3) questions triggered randomly or event-based 4) multiple questions over a certain period of time [Citation after Stone and Shiffmann 1994] (<<Salganik2018>>,109)", + parent=method2) + db.session.add(method3) + method3 = Method(id=51,name="wiki surveys", + description="Guide open-answer questions with user feedback.", + parent=method2) + db.session.add(method3) + method3 = Method(id=52,name="survey data linked to big data sources", + description="", + parent=method2) + db.session.add(method3) + method4 = Method(id=53,name="Enriched asking", + description="", + parent=method3) + db.session.add(method4) + method4 = Method(id=54,name="Amplified asking", + description="", + parent=method3) + db.session.add(method4) + method2 = Method(id=55,name="collaborative work", description="", parent=method1) db.session.add(method2) - method2 = Method(id=45,name="digital communication", + method3 = Method(id=56,name="open call projects", + description="(e.g. annotation).", + parent=method2) + db.session.add(method3) + method3 = Method(id=57,name="distributed data collection", + description="", + parent=method2) + db.session.add(method3) + method2 = Method(id=58,name="digital communication", description="", parent=method1) db.session.add(method2) - method1 = Method(id=46,name="statistical modeling", + method1 = Method(id=59,name="statistical modeling", description="", parent=method) db.session.add(method1) - method2 = Method(id=47,name="regression analysis", + method2 = Method(id=60,name="regression analysis", description="", parent=method1) db.session.add(method2) - method2 = Method(id=48,name="time-series analysis", + method2 = Method(id=61,name="time-series analysis", description="", parent=method1) db.session.add(method2) - method2 = Method(id=49,name="agent-based modeling", + method2 = Method(id=62,name="agent-based modeling", description="", parent=method1) db.session.add(method2) - method2 = Method(id=50,name="digital communication", - description="", - parent=method1) - db.session.add(method2) - method1 = Method(id=51,name="social complexity modeling/ social simulation", + method1 = Method(id=63,name="social complexity modeling/ social simulation", description="", parent=method) db.session.add(method1) + method2 = Method(id=64,name="nowcasting", + description="Using methods to predict the future for estimation of current values. (Example: predict influenza epidemiology combining CDC Data and Google Trends(<<Salganik2018>>,46–50)).", + parent=method1) + db.session.add(method2) reference = Reference(name="Rogers2013", cited="Rogers, R. (2013). Digital methods. Cambridge, Massachusetts, London, England: The MIT Press.") @@ -540,6 +585,24 @@ Furthermore the server-client-model is the established communication paradigms f reference = Reference(name="Wickham_etal2017", cited="Wickham, H., & Grolemund, G. (2017). R for Data Science: Import, tidy, transform, visualize, and model data. Beijing, Boston, Farnham, Sebastopol, Tokyo: O’Reilly UK Ltd.") db.session.add(reference) + reference = Reference(name="Papilloud_etal2018", + cited="Papilloud, C., & Hinneburg, A. (Eds.). (2018). Studienskripten zur Soziologie. Qualitative Textanalyse mit Topic-Modellen: Eine Einführung für Sozialwissenschaftler. Wiesbaden: Springer VS.") + db.session.add(reference) + reference = Reference(name="Maier_etal2018", + cited="Maier, D., Waldherr, A., Miltner, P., Wiedemann, G., Niekler, A., Keinert, A., . . . Adam, S. (2018). Applying LDA Topic Modeling in Communication Research: Toward a Valid and Reliable Methodology. Communication Methods and Measures, 12(2-3), 93–118. https://doi.org/10.1080/19312458.2018.1430754") + db.session.add(reference) + reference = Reference(name="Roberts2013", + cited="Roberts, M. E., Stewart, B. M., Tingley, D., Airoldi, E. M., & others (2013). The structural topic model and applied social science. In Advances in neural information processing systems workshop on topic models: computation, application, and evaluation (pp. 1–20).") + db.session.add(reference) + reference = Reference(name="Salganik2018", + cited="Salganik, M. J. (2018). Bit by bit: Social research in the digital age.") + db.session.add(reference) + reference = Reference(name="Cabrio2018", + cited="Cabrio, E., & Villata, S. (2018). Five years of argument mining: a data-driven analysis. In Proceedings of the 27th International Joint Conference on Artificial Intelligence (pp. 5427–5433).") + db.session.add(reference) + reference = Reference(name="Stab_etal2018", + cited="Stab, C., Daxenberger, J., Stahlhut, C., Miller, T., Schiller, B., Tauchmann, C., . . . Gurevych, I. (2018). ArgumenText: Searching for Arguments in Heterogeneous Sources. In Proceedings of the 2018 Conference of the North American Chapter of the Association for Computational Linguistics: Demonstrations (pp. 21–25).") + db.session.add(reference) diff --git a/sample_db.sqlite b/sample_db.sqlite index 5d3af6d1c13fadaf563d0433f45e9ec3486501ad..5062764ab07c1a65ebd949829019a7a2a00a8134 100644 GIT binary patch delta 8066 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 delta 2100 zcmZp8z}j$tb%K--=U)Z}1~CRW;G8p2$B2pZ@5Y2B{!xtlzd;gW{J+`Hu{W|aGrnMq zV(aH}X47Qd&-seAfmN2}0ZTQD5%Wog-OQDo^-QlADmjxlgE$>IRheco6){C|ax>ZR z7IM7dxWuuQH;iKzM;%8JhbynrL`UiA`}i51Cg1x!fAWVPkJw~1Ss5&Or`zi?ipy(= zsj)KX@-pV7mXzlgWh*4+CFWEXXBNw-%CRzd^G@H%&8WaG<1fp~U|2ofhnLZYza+Jy zL?NvxF+DdmucTOJ^NPS|Rwikq$<h&0@_JH+tPG~SjEU)~c_q3@iN&cY3c2|ysX3W> z>3UKI(;YP#C7Jak^(RG0FzZR{g7h$JN$5=fugNH`rX{P*%3#gQn39>ES(2EeP?TDn znpl*ap^%bVoSB}dC85E}V8}apV}!5^lavhDp5l_klFZ_g%;dxzxXYRNrCAx|c^M&^ zGxO4!_@r1FWO*4g^HNePKzwOQRt9t4=?VOdVw_BwVr&eSjjD{(9ReA}Chz&gvAH+0 zj+xPT`bP~$BPL0`$quoS?4nwrK;qn75nIQ^1$Gz%1A_q9rUVuNK34AS4E&RLFY=o4 zRC90NtSGRQd-L|{5Ee$k%{&c{`2~gT7#J8tH5nKfgcKMU82I=$DX=KmS@5+m@c-oh z%>Rb}8UKC$oBWsfPxBw)-^ah5e<S~D{w4f#`KR$u;P2#b;;-f};m_kw=TGF1;t%2X z<9FwGnC!Gcop~$Yy3IKoWclj3*jde)85!-_Sk0Ij8Clp^O_>=PC7D=Fm>C%@m{^UO z85!l7S&f((8MT>M44D}@Ragy}85t#*SoN718Ra-w^_UqM9hg{knHd@Fm{@g~85vcX zS+$uN8MT;LwU`+hm04LeK@=0K1~Vfg2NSD0Gb5udGpiaiBcnchJ*z4+BcnJos|qtC zBQrCrGKgYgRbpmjlx1dBWM*U(V`f!gW@OZ4W0hxSWMpDym1AaPRAOe8WoBg5U}lwJ zW@OZ3W|3xQ<YZ@&VrJx&VU=WNWK?8gkzi)zbYc}}W@I#BW)Wj%<m6!$WoBgLVPX{l zQOvBu%#4in*37Iz%#4iIEG&Y|jGXGM0?dqzx@@fcAc~2VkC~B?jfsVqnUT|&g@>7u zQ<{~VnUPVAnT3m)laZN~lbMlGhl!PgnUT?vla(DrF|)8SGjfWsvNAI=vNEx-Ff(#; zvobR?GIBGsFflW7DzGv#Gcrmsu`)0)F!Hm3Gp@*Ho&#U?W0`CixEP}t`0RN*xL(m9 zCv!2qVt8mA%~+J0Gr94z#PkRJj0!AFuNdY}e)M@ZICpcnvIJYgayKV8izz9&TNo{O zbE+~goxCQnn}xHUS!}X(kPi#zE2d+cyMsP4vT(j)xUjkLOEVL*663$gvJn!bWPesY z-Y~{KkS^j20Nz4I3s?cbz`!VH%F9TF0)T;m;Rs3rFu6C<m4$(UA#(GzpXSVrX_NV4 z4U`xd7>rDLIg#yy6>JO)4C1=Hkiuc|xBq<0SsWM@ma`Zz3T$UdVEn_sO@NU_AWW0h zfq{P(zX9KMz8QR`ygzs+^Lp_-<Z<Hu#NE#IgKHbtOs-C@3a%I~Zq6s1hdB&5c-ddG zpJsi*+QYe;)q!&&XEA3Cr!A*4CmY9Wjtd-HIA(BEbGUDgI`E8z%}bq?!8LX|j~=6} zVOnNhN@iZVLRw;SNoit^LS|lCeo<~>NoIbYLS~*qT2X4M0w|KzqEy)!8jX1wrwi&c z3Qs?)$LPQ!*PNiTeFrn+eMSyBIb~J`TVBTLb)1Y6>~ac9tPGC4)A^YhrP<{a6<Haa zd8aR6Wt5SU6IEbk(B@?<$uG!EhLrW7!d}h@T7Z&MF+d7kCbas&mQeiwEu@Q+GgI@D zQxyt|5|c|ZlT(puJ9Z639U{+!tLBrJXJxSGoqmUjQHos-S2f573PVISIDH{6qpUh9 jHP7aSpKKW?n+FAet7ZAWnVUZZ^)gPr6d5_WHsk^TJy+@$ -- GitLab