diff --git a/.gitignore b/.gitignore index fb69f59..56630f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ __pycache__ client/examples/* client/komrade.json +uploads +uploads/* +uploads/*/* \ No newline at end of file diff --git a/client/log.txt b/client/log.txt index 9ba9d16..59be20f 100644 --- a/client/log.txt +++ b/client/log.txt @@ -1,20 +1,8 @@ -[True, True] -['build', True] - - - - - - - - - - - - - - - -['_ButtonBehavior__state_event', '_ButtonBehavior__touch_time', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__events__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__metaclass__', '__module__', '__ne__', '__new__', '__proxy_getter', '__proxy_setter', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_apply_transform', '_call_get_bg_color_disabled', '_call_get_bg_color_down', '_context', '_current_button_color', '_disabled_count', '_disabled_value', '_do_press', '_do_release', '_doing_ripple', '_fading_out', '_finish_init', '_finishing_ripple', '_get_md_bg_color_disabled', '_get_md_bg_color_down', '_kwargs_applied_init', '_md_bg_color_disabled', '_md_bg_color_down', '_no_ripple_effect', '_proxy_ref', '_ripple_rad', '_set_color', '_set_ellipse', '_set_md_bg_color_disabled', '_set_md_bg_color_down', '_trigger_layout', '_update_color', '_update_specific_text_color', '_walk', '_walk_reverse', 'a', 'add_widget', 'always_release', 'anchor_x', 'anchor_y', 'anim_complete', 'apply_class_lang_rules', 'apply_property', 'b', 'background_hue', 'background_palette', 'bind', 'cancel_event', 'canvas', 'center', 'center_x', 'center_y', 'children', 'clear_widgets', 'cls', 'collide_point', 'collide_widget', 'create_property', 'dec_disabled', 'device_ios', 'disabled', 'dispatch', 'dispatch_children', 'dispatch_generic', 'do_layout', 'events', 'export_as_image', 'export_to_png', 'fade_out', 'fbind', 'finish_ripple', 'font_name', 'font_size', 'funbind', 'g', 'get_center_x', 'get_center_y', 'get_disabled', 'get_parent_window', 'get_property_observers', 'get_right', 'get_root_window', 'get_top', 'get_window_matrix', 'getter', 'height', 'icon', 'id', 'ids', 'inc_disabled', 'is_event_type', 'last_touch', 'lay_canvas_instructions', 'layout_hint_with_bounds', 'lbl_txt', 'md_bg_color', 'md_bg_color_disabled', 'md_bg_color_down', 'min_state_time', 'on_disabled', 'on_kv_post', 'on_md_bg_color', 'on_opacity', 'on_press', 'on_release', 'on_touch_down', 'on_touch_move', 'on_touch_up', 'opacity', 'opposite_colors', 'padding', 'parent', 'pos', 'pos_hint', 'properties', 'property', 'proxy_ref', 'r', 'radius', 'register_event_type', 'remove_widget', 'right', 'ripple_alpha', 'ripple_color', 'ripple_duration_in_fast', 'ripple_duration_in_slow', 'ripple_duration_out', 'ripple_func_in', 'ripple_func_out', 'ripple_rad_default', 'ripple_scale', 'set_center_x', 'set_center_y', 'set_disabled', 'set_right', 'set_top', 'setter', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'specific_secondary_text_color', 'specific_text_color', 'start_ripple', 'state', 'text_color', 'theme_cls', 'theme_text_color', 'to_local', 'to_parent', 'to_widget', 'to_window', 'top', 'trigger_action', 'uid', 'unbind', 'unbind_uid', 'unregister_event_types', 'user_font_size', 'walk', 'walk_reverse', 'width', 'x', 'y'] -['_ButtonBehavior__state_event', '_ButtonBehavior__touch_time', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__events__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__metaclass__', '__module__', '__ne__', '__new__', '__proxy_getter', '__proxy_setter', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_apply_transform', '_call_get_bg_color_disabled', '_call_get_bg_color_down', '_context', '_current_button_color', '_disabled_count', '_disabled_value', '_do_press', '_do_release', '_doing_ripple', '_fading_out', '_finish_init', '_finishing_ripple', '_get_md_bg_color_disabled', '_get_md_bg_color_down', '_kwargs_applied_init', '_md_bg_color_disabled', '_md_bg_color_down', '_no_ripple_effect', '_proxy_ref', '_ripple_rad', '_set_color', '_set_ellipse', '_set_md_bg_color_disabled', '_set_md_bg_color_down', '_trigger_layout', '_update_color', '_update_specific_text_color', '_walk', '_walk_reverse', 'a', 'add_widget', 'always_release', 'anchor_x', 'anchor_y', 'anim_complete', 'apply_class_lang_rules', 'apply_property', 'b', 'background_hue', 'background_palette', 'bind', 'cancel_event', 'canvas', 'center', 'center_x', 'center_y', 'children', 'clear_widgets', 'cls', 'collide_point', 'collide_widget', 'create_property', 'dec_disabled', 'device_ios', 'disabled', 'dispatch', 'dispatch_children', 'dispatch_generic', 'do_layout', 'events', 'export_as_image', 'export_to_png', 'fade_out', 'fbind', 'finish_ripple', 'font_name', 'font_size', 'funbind', 'g', 'get_center_x', 'get_center_y', 'get_disabled', 'get_parent_window', 'get_property_observers', 'get_right', 'get_root_window', 'get_top', 'get_window_matrix', 'getter', 'height', 'icon', 'id', 'ids', 'inc_disabled', 'is_event_type', 'last_touch', 'lay_canvas_instructions', 'layout_hint_with_bounds', 'lbl_txt', 'md_bg_color', 'md_bg_color_disabled', 'md_bg_color_down', 'min_state_time', 'on_disabled', 'on_kv_post', 'on_md_bg_color', 'on_opacity', 'on_press', 'on_release', 'on_touch_down', 'on_touch_move', 'on_touch_up', 'opacity', 'opposite_colors', 'padding', 'parent', 'pos', 'pos_hint', 'properties', 'property', 'proxy_ref', 'r', 'radius', 'register_event_type', 'remove_widget', 'right', 'ripple_alpha', 'ripple_color', 'ripple_duration_in_fast', 'ripple_duration_in_slow', 'ripple_duration_out', 'ripple_func_in', 'ripple_func_out', 'ripple_rad_default', 'ripple_scale', 'set_center_x', 'set_center_y', 'set_disabled', 'set_right', 'set_top', 'setter', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'specific_secondary_text_color', 'specific_text_color', 'start_ripple', 'state', 'text_color', 'theme_cls', 'theme_text_color', 'to_local', 'to_parent', 'to_widget', 'to_window', 'top', 'trigger_action', 'uid', 'unbind', 'unbind_uid', 'unregister_event_types', 'user_font_size', 'walk', 'walk_reverse', 'width', 'x', 'y'] -['_ButtonBehavior__state_event', '_ButtonBehavior__touch_time', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__events__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__metaclass__', '__module__', '__ne__', '__new__', '__proxy_getter', '__proxy_setter', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_apply_transform', '_call_get_bg_color_disabled', '_call_get_bg_color_down', '_context', '_current_button_color', '_disabled_count', '_disabled_value', '_do_press', '_do_release', '_doing_ripple', '_fading_out', '_finish_init', '_finishing_ripple', '_get_md_bg_color_disabled', '_get_md_bg_color_down', '_kwargs_applied_init', '_md_bg_color_disabled', '_md_bg_color_down', '_no_ripple_effect', '_proxy_ref', '_ripple_rad', '_set_color', '_set_ellipse', '_set_md_bg_color_disabled', '_set_md_bg_color_down', '_trigger_layout', '_update_color', '_update_specific_text_color', '_walk', '_walk_reverse', 'a', 'add_widget', 'always_release', 'anchor_x', 'anchor_y', 'anim_complete', 'apply_class_lang_rules', 'apply_property', 'b', 'background_hue', 'background_palette', 'bind', 'cancel_event', 'canvas', 'center', 'center_x', 'center_y', 'children', 'clear_widgets', 'cls', 'collide_point', 'collide_widget', 'create_property', 'dec_disabled', 'device_ios', 'disabled', 'dispatch', 'dispatch_children', 'dispatch_generic', 'do_layout', 'events', 'export_as_image', 'export_to_png', 'fade_out', 'fbind', 'finish_ripple', 'font_name', 'font_size', 'funbind', 'g', 'get_center_x', 'get_center_y', 'get_disabled', 'get_parent_window', 'get_property_observers', 'get_right', 'get_root_window', 'get_top', 'get_window_matrix', 'getter', 'height', 'icon', 'id', 'ids', 'inc_disabled', 'is_event_type', 'last_touch', 'lay_canvas_instructions', 'layout_hint_with_bounds', 'lbl_txt', 'md_bg_color', 'md_bg_color_disabled', 'md_bg_color_down', 'min_state_time', 'on_disabled', 'on_kv_post', 'on_md_bg_color', 'on_opacity', 'on_press', 'on_release', 'on_touch_down', 'on_touch_move', 'on_touch_up', 'opacity', 'opposite_colors', 'padding', 'parent', 'pos', 'pos_hint', 'properties', 'property', 'proxy_ref', 'r', 'radius', 'register_event_type', 'remove_widget', 'right', 'ripple_alpha', 'ripple_color', 'ripple_duration_in_fast', 'ripple_duration_in_slow', 'ripple_duration_out', 'ripple_func_in', 'ripple_func_out', 'ripple_rad_default', 'ripple_scale', 'set_center_x', 'set_center_y', 'set_disabled', 'set_right', 'set_top', 'setter', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'specific_secondary_text_color', 'specific_text_color', 'start_ripple', 'state', 'text_color', 'theme_cls', 'theme_text_color', 'to_local', 'to_parent', 'to_widget', 'to_window', 'top', 'trigger_action', 'uid', 'unbind', 'unbind_uid', 'unregister_event_types', 'user_font_size', 'walk', 'walk_reverse', 'width', 'x', 'y'] +content: eeee +img_src: ['/home/ryan/Pictures/the-first-1940s-coders-were-womenso-how-did-tech-bros-take-overs-featured-photo.jpg'] +/home/ryan/Pictures/the-first-1940s-coders-were-womenso-how-did-tech-bros-take-overs-featured-photo.jpg +dict_keys(['postbox', 'post_content_input', 'buttonbox', 'file_chooser_button', 'post_status']) +got back from post: 53 +content: eeee +img_src: [] +got back from post: 54 diff --git a/client/main.kv b/client/main.kv index fb851d5..e507eca 100644 --- a/client/main.kv +++ b/client/main.kv @@ -85,6 +85,9 @@ MyLayout: LoginScreen: FeedScreen: - PostScreen: + + AddPostScreen: + id: add_post_screen + MessagesScreen: NotificationsScreen: diff --git a/client/main.py b/client/main.py index 6cf39da..ca5b05e 100644 --- a/client/main.py +++ b/client/main.py @@ -7,7 +7,7 @@ from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout from kivymd.theming import ThemeManager from kivy.properties import ObjectProperty,ListProperty -import time +import time,os from collections import OrderedDict from functools import partial from kivy.uix.screenmanager import NoTransition @@ -111,7 +111,7 @@ class MainApp(MDApp): if not self.is_logged_in(): self.root.change_screen('login') else: - self.root.change_screen('feed') + self.root.change_screen('post') return self.root def is_logged_in(self): @@ -152,6 +152,39 @@ class MainApp(MDApp): else: self.root.ids.login_status.text=res.text + def post(self, content='', img_src=[]): + log('content: '+str(content)) + log('img_src: '+str(img_src)) + + jsond = {'content':str(content)} + + # upload? + filename=img_src[0] if img_src and os.path.exists(img_src[0]) else '' + + url_upload=self.api+'/upload' + url_post = self.api+'/post' + + server_filename='' + + if filename: + with self.get_session() as sess: + #res = sess.post(url, files=filesd, data={'data':json.dumps(jsond)}, headers=headers) + log(filename) + self.root.ids.add_post_screen.ids.post_status.text='Uploading file' + r = sess.post(url_upload,files={'file':open(filename,'rb')}) + if r.status_code==200: + server_filename = r.text + self.root.ids.add_post_screen.ids.post_status.text='File uploaded' + + with self.get_session() as sess: + # add post + #log(self.root.ids.add_post_screen.ids.keys()) + self.root.ids.add_post_screen.ids.post_status.text='Creating post' + jsond={'img_src':server_filename, 'content':content} + r = sess.post(url_post, json=jsond) + log('got back from post: ' + r.text) + + self.root.ids.add_post_screen.ids.post_status.text='Post created' diff --git a/client/screens/post/post.kv b/client/screens/post/post.kv index 50cfd33..22343cf 100644 --- a/client/screens/post/post.kv +++ b/client/screens/post/post.kv @@ -1,4 +1,69 @@ -#:import PostScreen screens.post.post.PostScreen +#:import AddPostScreen screens.post.post.AddPostScreen +#:import ViewPostScreen screens.post.post.ViewPostScreen -: - name: 'post' \ No newline at end of file + +: + name: 'post' + id: post_screen + + MyBoxLayout: + id: postbox + size_hint:0.5,0.18 + + # FileChoose: + # size_hint_y: 0.1 + # on_release: self.choose() + # text: 'Select a file' + + MDTextField: + id: post_content_input + hint_text: "word?" + required: False + # write_tab: False + multiline: True + helper_text_mode: "on_error" + color_mode: 'custom' + line_color_focus: 1,0,0,1 + line_color_normal: 1,0,0,1 + current_hint_text_color: 1,0,0,1 + + + + + MDBoxLayout: + id: buttonbox + size_hint_y: None + adaptive_width: True + height: '56dp' + spacing: '10dp' + pos_hint: {'center_x': .5} + + FileChoose: + id: file_chooser_button + text: "upload" + on_release: self.choose() + #app.register(username.text, password.text) + theme_text_color: "Custom" + text_color: 1,0,0,1 + md_bg_color: 0,0,0,1 + + MDRectangleFlatButton: + text: "post" + on_release: app.post(post_content_input.text, file_chooser_button.selection) + #app.root.change_screen("welcome") + theme_text_color: "Custom" + text_color: 1,0,0,1 + md_bg_color: 0,0,0,1 + + + + + MDLabel: + id: post_status + text:"" + theme_text_color: 'Error' + pos_hint:{'center_x':.5} + + +: + name: 'view' \ No newline at end of file diff --git a/client/screens/post/post.py b/client/screens/post/post.py index 358a639..081a655 100644 --- a/client/screens/post/post.py +++ b/client/screens/post/post.py @@ -1,3 +1,40 @@ from screens.base import ProtectedScreen +from plyer import filechooser +from kivymd.uix.button import MDRectangleFlatButton, MDIconButton +from kivy.properties import ListProperty +from kivy.app import App -class PostScreen(ProtectedScreen): pass + + +class FileChoose(MDRectangleFlatButton): + ''' + Button that triggers 'filechooser.open_file()' and processes + the data response from filechooser Activity. + ''' + + selection = ListProperty([]) + + def choose(self): + ''' + Call plyer filechooser API to run a filechooser Activity. + ''' + filechooser.open_file(on_selection=self.handle_selection) + + def handle_selection(self, selection): + ''' + Callback function for handling the selection response from Activity. + ''' + self.selection = selection + + def on_selection(self, *a, **k): + ''' + Update TextInput.text after FileChoose.selection is changed + via FileChoose.handle_selection. + ''' + pass + #App.get_running_app().root.ids.result.text = str(self.selection) + + +class AddPostScreen(ProtectedScreen): pass + +class ViewPostScreen(ProtectedScreen): pass diff --git a/server/models.py b/server/models.py index 08c14db..94542ca 100644 --- a/server/models.py +++ b/server/models.py @@ -21,6 +21,7 @@ class Person(MyGraphObject): followers = RelatedFrom('Person','FOLLOWS') based_in = RelatedTo('Place','BASED_IN') groups = RelatedTo('Group','IN_GROUP') + avatar = RelatedTo('Media','HAS_MEDIA') class Post(MyGraphObject): # properties @@ -32,6 +33,7 @@ class Post(MyGraphObject): author = RelatedFrom('Person','WROTE') location = RelatedTo('Place','BASED_IN') + @property def data(self): dx=dict(self.__ogm__.node) diff --git a/server/server.py b/server/server.py index 82c364f..7624b3d 100644 --- a/server/server.py +++ b/server/server.py @@ -4,7 +4,8 @@ from pathlib import Path from models import * from flask_api import FlaskAPI, status, exceptions from werkzeug.security import generate_password_hash,check_password_hash - +from flask import Flask, flash, request, redirect, url_for +from werkzeug.utils import secure_filename # works better with tor? @@ -16,6 +17,8 @@ jsonify = str app = flask.Flask(__name__) app.config["DEBUG"] = True +app.config['UPLOAD_DIR'] = 'uploads/' +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} @app.route('/') def home(): return '404 go home friend' @@ -76,9 +79,64 @@ def register(): ## CREATE +def allowed_file(filename): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +def get_random_filename(filename): + import uuid + fn=uuid.uuid4().hex + return (fn[:3],fn[3:]+os.path.splitext(filename)[-1]) + +@app.route('/api/upload',methods=['POST']) +def upload_file(): + files = request.files + # check if the post request has the file part + if 'file' not in request.files: + return 'No file found',status.HTTP_204_NO_CONTENT + + file = request.files['file'] + + # if user does not select file, browser also + # submit an empty part without filename + print('filename!',file.filename) + if file.filename == '': + return 'No filename',status.HTTP_206_PARTIAL_CONTENT + + if file and allowed_file(file.filename): + prefix,filename = get_random_filename(file.filename) #secure_filename(file.filename) + #odir = os.path.join(app.config['UPLOAD_DIR'], os.path.dirname(filename)) + #if not os.path.exists(odir): + folder = os.path.join(app.config['UPLOAD_DIR'], prefix) + if not os.path.exists(folder): os.mkdir(folder) + file.save(os.path.join(folder, filename)) + #return redirect(url_for('uploaded_file', filename=filename)) + return prefix+'/'+filename, status.HTTP_200_OK + + # print('RECEIVED:',files['file']) + return 'Post created',status.HTTP_200_OK + + @app.route('/api/post',methods=['POST']) def create_post(): data=request.json + # print(dir(request)) + #files = request.files + #print(request.json) + print(data) + #print(request.files) + + post = Post() + post.content = data.get('content','') + post.img_src = data.get('img_src','') + G.push(post) + #print(dir(post.__ogm__.node)) + + post_id=str(post.__ogm__.node.identity) + + # print('RECEIVED:',files['file']) + return post_id,status.HTTP_200_OK + ### READ