Compare commits

...
Sign in to create a new pull request.

56 commits

Author SHA1 Message Date
62e307f6a2 show user donations on his own page 2024-09-26 15:28:52 +03:00
cf4b642461 remove test page 2024-09-26 15:17:31 +03:00
1d313233a6 add arba yesodot access from homepage 2024-09-24 14:37:34 +03:00
4aca571930 unarchive campaign 2024-09-24 14:37:11 +03:00
88ad7d4908 ambassador 2024-09-24 14:36:54 +03:00
12777b49a4 ambassador 2024-09-24 14:36:35 +03:00
959c98fe79 functions for campaign model and ambassador goal 2024-09-24 14:36:24 +03:00
d2a04f8b5a add archive access 2024-09-24 14:35:55 +03:00
82cd51b805 register arba yesodot blueprint 2024-09-24 12:49:12 +03:00
ba10625f3d Merge branch 'master' of https://github.com/ydb5755/PilznoProduction 2024-09-24 12:39:56 +03:00
90c0f3ae98 arba yesodot blueprint init 2024-09-24 12:39:53 +03:00
d6c532ea49 admin change for users and add campaign button 2024-09-24 12:39:42 +03:00
2d3c542b32 admin change for users and add campaign button 2024-09-24 11:42:40 +03:00
7ada4b2a6f create campaign form start 2024-09-24 11:42:21 +03:00
93dd70849a delete testing page 2024-09-24 11:42:03 +03:00
c86acc10ca admin buttons 2024-09-24 11:41:46 +03:00
fde7fdd50a user api init and update admin status 2024-09-24 11:41:17 +03:00
96c553fd36 move campaign api calls to their own blueprint 2024-09-24 08:22:01 +03:00
3f0c84566e add user type to users on admin page 2024-09-23 19:54:10 +03:00
b225349931 update active status and archive - api calls 2024-09-23 19:49:07 +03:00
460a9a06cd add default values for db pop 2024-09-23 12:46:58 +03:00
879dda4891 adjust db population to account for each of 3 local machine paths 2024-09-23 12:27:57 +03:00
b6a8c6c53b ignore pycache 2024-09-23 10:44:15 +03:00
f4d5e0a690 ignore pycache 2024-09-23 10:43:59 +03:00
fc90674ccc change activity buttons added event listeners 2024-09-22 15:52:05 +03:00
fdcdcf1c4c cpython 2024-09-22 15:08:29 +03:00
a4661a1e0b adding js to start work on archive buttons and change activity status 2024-09-22 15:08:21 +03:00
33cfbbcc9f archiving campaign 2024-09-22 15:07:15 +03:00
45e9b3a9bc cpythons 2024-09-20 12:03:02 +03:00
ab8e276db8 start adding funciton to add ambassador reltionships 2024-09-20 12:02:54 +03:00
8a3d082458 remove non relevant info from usr page 2024-09-20 12:02:36 +03:00
57c0e317cc change usr repr 2024-09-20 12:02:18 +03:00
816c1d697e add anonymous option to donation 2024-09-20 12:02:01 +03:00
5582af5ee7 start campaign page 2024-09-20 12:01:48 +03:00
0b78e121a2 add goal to campaign 2024-09-20 12:01:29 +03:00
39e5127f3f active campaign to html 2024-09-20 12:01:18 +03:00
d3d53b434f requirements 2024-09-19 15:23:11 +03:00
42f76f1987 cpythons 2024-09-18 18:49:08 +03:00
bdde079e43 add campaigns to homepage 2024-09-18 18:48:59 +03:00
528d62010b add campaign detail page route 2024-09-18 18:48:35 +03:00
0b17e39074 add campaign detail page route 2024-09-18 18:48:26 +03:00
d0908cf65d implementing nested blueprint for api calls 2024-09-17 11:34:48 +03:00
fbdc6b4ee4 finish drop and insert funcitons for preloading db 2024-09-17 11:08:23 +03:00
450b06c185 add active to campaign model 2024-09-17 11:07:57 +03:00
bdf909c143 fix homepage url 2024-09-17 11:07:19 +03:00
25bafd04e0 add admin page for viewing models 2024-09-17 11:06:51 +03:00
2ca4812480 making fake data to insert 2024-09-16 15:34:31 +03:00
71d1910988 ambassador map and replace relationships with integer fields 2024-09-16 14:23:24 +03:00
67264b3da6 add repr to class models and exploring relationships 2024-09-15 15:25:27 +03:00
936a88f264 relationship needs to be class name not tablename 2024-09-15 13:51:38 +03:00
f20458ae1c import models to init 2024-09-15 11:21:04 +03:00
300331b2f2 pycache 2024-09-15 11:14:52 +03:00
0b1fde2066 Merge branch 'master' of https://github.com/ydb5755/PilznoProduction 2024-09-13 12:02:54 +03:00
b186239f04 donation model 2024-09-13 12:00:06 +03:00
359d7e8c81 delete 2024-09-05 11:15:09 +03:00
a608576bc5 test commit 2024-09-05 11:11:23 +03:00
51 changed files with 948 additions and 59 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
/__pycache__
**/__pycache__
/errorlog.txt
/instance
/logs.log

View file

@ -32,10 +32,15 @@ def create_app():
app.wsgi_app, x_for=1, x_proto=1
)
from app.users.models import User
import app.users.models as user_models
import app.campaigns.models as campaign_models
import app.main.models as main_models
@login_manager.user_loader
def load_user(user_id):
user = User.query.get(user_id)
user = user_models.User.query.get(user_id)
if user:
return user
else:
@ -43,17 +48,22 @@ def create_app():
login_manager.login_view = 'users.login'
from app.users import users
from app.users.users_api import users_api
from app.main import main
from app.campaigns import campaigns
from app.campaigns.campaign_api import campaign_api
from app.admin import admin
# from app.agent_reports import agent_reports
# from app.status_reports import status_reports
from app.arba_yesodot import arba_yesodot
campaigns.register_blueprint(campaign_api)
users.register_blueprint(users_api)
app.register_blueprint(users)
app.register_blueprint(main)
app.register_blueprint(campaigns)
app.register_blueprint(admin)
# app.register_blueprint(agent_reports)
# app.register_blueprint(status_reports)
app.register_blueprint(arba_yesodot)
@app.route('/')
def reroute_base_url():

View file

@ -1,6 +1,8 @@
from app import db
from app.admin import admin
from app.users.models import User
from app.campaigns.models import Campaign
from app.main.models import Donation
from app.users.forms import LoginForm, RegisterUserForm#, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user
@ -10,4 +12,14 @@ import os
@admin.route('administration')
def administration():
return render_template('administration.html')
users = User.query.all()
campaigns = Campaign.query.filter_by(archived=False).all()
return render_template('administration.html',
users=users,
campaigns=campaigns)
@admin.route('archive')
def archive():
archived_campaigns = Campaign.query.filter_by(archived=True).all()
return render_template('archive.html',
archived_campaigns=archived_campaigns)

98
app/admin/static/admin.js Normal file
View file

@ -0,0 +1,98 @@
const archiveButtons = document.getElementsByClassName('archive-button');
const activityButtons = document.getElementsByClassName('change-activity-btn');
const adminButtons = document.getElementsByClassName('change-admin-btn');
function deactivateAdminButtons(){
for (let i = 0; i < adminButtons.length; i++){
adminButtons[i].disabled=true;
}
}
function activateAdminButtons(){
for (let i = 0; i < adminButtons.length; i++){
adminButtons[i].disabled=false;
}
}
function deactivateArchiveButtons(){
for (let i = 0; i < archiveButtons.length; i++){
archiveButtons[i].disabled=true;
}
}
function activateArchiveButtons(){
for (let i = 0; i < archiveButtons.length; i++){
archiveButtons[i].disabled=false;
}
}
function deactivateActiveStatusCheckboxes(){
for (let i = 0; i < activityButtons.length; i++){
activityButtons[i].disabled=true;
}
}
function activateActiveStatusCheckboxes(){
for (let i = 0; i < activityButtons.length; i++){
activityButtons[i].disabled=false;
}
}
function activateAllButtons(){
activateAdminButtons()
activateArchiveButtons()
activateActiveStatusCheckboxes()
}
function deactivateAllButtons(){
deactivateAdminButtons()
deactivateArchiveButtons()
deactivateActiveStatusCheckboxes()
}
async function updateActiveStatus(id, status) {
deactivateAllButtons()
var result = await fetch(`/campaigns/campaign_api/update_active_status/${id}/${status}`, {method:'PUT'});
var data = await result.json();
if (status === true){
status = 'True'
} else {
status = 'False'
}
document.getElementById(`${id}-active-status`).innerText = status;
activateAllButtons()
}
async function archiveCampaign(id){
deactivateAllButtons()
var result = await fetch(`/campaigns/campaign_api/archive_campaign/${id}`, {method:'PUT'});
var data = await result.json();
document.getElementById(`${id}-row`).remove();
activateAllButtons()
}
async function updateAdminStatus(id, status) {
deactivateAllButtons()
var result = await fetch(`/users/users_api/update_admin_status/${id}/${status}`, {method:'PUT'});
var data = await result.json();
if (status === true){
status = 'True'
} else {
status = 'False'
}
document.getElementById(`${id}-admin-status`).innerText = status;
activateAllButtons()
}
document.addEventListener("DOMContentLoaded", (event) => {
for(let i = 0; i < activityButtons.length; i++){
activityButtons[i].addEventListener('change', e => {
updateActiveStatus(parseInt(e.target.value), e.target.checked)
})
}
for(let i = 0; i < archiveButtons.length; i++){
archiveButtons[i].addEventListener('click', e => {
archiveCampaign(parseInt(e.target.value))
})
}
for(let i = 0; i < adminButtons.length; i++){
adminButtons[i].addEventListener('change', e => {
updateAdminStatus(parseInt(e.target.value), e.target.checked)
})
}
});

View file

@ -0,0 +1,56 @@
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}

View file

@ -0,0 +1,31 @@
const unArchiveButtons = document.getElementsByClassName('unarchive-button');
function deactivateUnArchiveButtons(){
for (let i = 0; i < unArchiveButtons.length; i++){
unArchiveButtons[i].disabled=true;
}
}
function activateUnArchiveButtons(){
for (let i = 0; i < unArchiveButtons.length; i++){
unArchiveButtons[i].disabled=false;
}
}
async function unArchiveCampaign(id){
deactivateUnArchiveButtons()
var result = await fetch(`/campaigns/campaign_api/un_archive_campaign/${id}`, {method:'PUT'});
var data = await result.json();
document.getElementById(`${id}-row`).remove();
activateUnArchiveButtons()
}
document.addEventListener("DOMContentLoaded", (event) => {
for(let i = 0; i < unArchiveButtons.length; i++){
unArchiveButtons[i].addEventListener('click', e => {
unArchiveCampaign(parseInt(e.target.value))
})
}
});

View file

@ -1,2 +1,90 @@
{% extends 'base.html' %}
{% block title %}Administration{% endblock title %}
{% block stylesheet %}
<link rel="stylesheet" href="{{ url_for('admin.static', filename='administration.css') }}">
{% endblock stylesheet %}
{% block content %}
<main>
<div class="container my-4 px-5">
<h2 class="text-center">Users</h2>
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Is Admin?</th>
<th>Change Admin Status</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{user.id}}</td>
<td>{{user.first_name}}</td>
<td>{{user.last_name}}</td>
<td>{{user.email}}</td>
<td id="{{user.id}}-admin-status">{% if user.user_type == 'Admin' %}True{%else%}False{%endif%}</td>
<td>
<label class="switch">
<input
type="checkbox"
value="{{user.id}}"
id="admin-checkbox-{{user.id}}"
class="change-admin-btn"
{%if user.user_type == 'Admin'%}checked{%endif%}>
<span class="slider round"></span>
</label>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="container my-4 px-5">
<div class="d-flex justify-content-center align-items-center">
<h2 class="me-3">Campaigns</h2>
<a href="{{url_for('campaigns.add_campaign')}}" class="btn btn-primary">New Campaign</a>
</div>
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Title</th>
<th>Active</th>
<th>Change Active Status</th>
<th>Archive?</th>
</tr>
</thead>
<tbody>
{% for campaign in campaigns %}
<tr id="{{campaign.id}}-row" >
<td>{{campaign.id}}</td>
<td>{{campaign.title}}</td>
<td id="{{campaign.id}}-active-status">{{campaign.active}}</td>
<td>
<label class="switch">
<input
type="checkbox"
value="{{campaign.id}}"
id="active-checkbox-{{campaign.id}}"
class="change-activity-btn"
{%if campaign.active%}checked{%endif%}>
<span class="slider round"></span>
</label>
</td>
<td><button
id="archive-button-{{campaign.id}}"
value="{{campaign.id}}"
class="archive-button btn btn-primary">Click to archive</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</main>
<script src="{{url_for('.static', filename='admin.js')}}"></script>
{% endblock content %}

View file

@ -0,0 +1,38 @@
{% extends 'base.html' %}
{% block title %}Archive{% endblock title %}
{% block stylesheet %}{% endblock stylesheet %}
{% block content %}
<main>
<div class="container my-4 px-5">
<h2 class="text-center">Archived Campaigns</h2>
<table class="table table-bordered table-striped custom-table">
<thead class="table-dark">
<tr>
<th>Title</th>
<th>Goal</th>
<th>Raised</th>
<th>Campaign Link</th>
<th>Unarchive?</th>
</tr>
</thead>
<tbody>
{% for campaign in archived_campaigns %}
<tr id="{{campaign.id}}-row">
<td>{{campaign.title}}</td>
<td>{{campaign.goal}}</td>
<td>{{campaign.get_amount_raised()}}</td>
<td><a href="{{url_for('campaigns.campaign_page', campaign_id=campaign.id)}}">See campaign details</a></td>
<td><button
id="unarchive-button-{{campaign.id}}"
value="{{campaign.id}}"
class="unarchive-button btn btn-primary">Click to un-archive</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</main>
<script src="{{url_for('.static', filename='archive.js')}}"></script>
{% endblock content %}

View file

@ -0,0 +1,9 @@
from flask import Blueprint
arba_yesodot = Blueprint('arba_yesodot',
__name__,
template_folder='templates',
static_folder='static',
url_prefix='/arba_yesodot')
from app.arba_yesodot import routes

View file

@ -0,0 +1,15 @@
from app import db
from app.arba_yesodot import arba_yesodot
from app.users.models import User
from app.campaigns.models import Campaign
from app.campaigns.forms import CreateCampaignForm
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user
from werkzeug.security import check_password_hash, generate_password_hash
from datetime import datetime
import os
from time import sleep
@arba_yesodot.route('arba_yesodot_homepage')
def arba_yesodot_homepage():
return render_template('arba_yesodot_homepage.html')

View file

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block title %}Arba Yesodot{% endblock title %}
{% block stylesheet %}{% endblock stylesheet %}
{% block content %}
{% endblock content %}

View file

@ -0,0 +1,9 @@
from flask import Blueprint
campaign_api = Blueprint('campaign_api',
__name__,
template_folder='templates',
static_folder='static',
url_prefix='/campaign_api')
from app.campaigns.campaign_api import routes

View file

@ -0,0 +1,30 @@
from app import db
from flask import render_template
from app.campaigns.campaign_api import campaign_api
from app.campaigns.models import Campaign
from time import sleep
@campaign_api.route('update_active_status/<id>/<status>', methods=['PUT'])
def update_active_status(id, status):
if status == 'true':
status = True
else:
status = False
Campaign.query.filter_by(id=id).update({'active':status})
db.session.commit()
sleep(1)
return {'status':'success'}
@campaign_api.route('archive_campaign/<id>', methods=['PUT'])
def archive_campaign(id):
Campaign.query.filter_by(id=id).update({'archived': True, 'active':False})
db.session.commit()
return {'status':'success'}
@campaign_api.route('un_archive_campaign/<id>', methods=['PUT'])
def un_archive_campaign(id):
Campaign.query.filter_by(id=id).update({'archived': False, 'active':False})
db.session.commit()
return {'status':'success'}

View file

@ -0,0 +1,20 @@
from flask_wtf import FlaskForm
from wtforms import StringField, \
EmailField, \
PasswordField, \
SubmitField, \
SelectField, \
BooleanField,\
DateField, IntegerField
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms.validators import DataRequired, ValidationError, NumberRange, EqualTo, Email
from app.campaigns.models import Campaign
from flask_login import current_user
# import logging
# logging.basicConfig(filename='logs.log', encoding='utf-8', level=logging.DEBUG)
class CreateCampaignForm(FlaskForm):
title = StringField('Title', validators=[DataRequired()])
active = BooleanField('Initialize as active?')
goal = IntegerField('Goal')
submit = SubmitField('Add Campaign')

View file

@ -6,9 +6,37 @@ import jwt
from datetime import datetime, timezone, timedelta
class AmbassadorMap(db.Model):
__tablename__ = 'ambassador_map'
id = Column('id', INTEGER(), primary_key=True)
campaign_id = Column('campaign_id', INTEGER(), nullable=False)
user_id = Column('user_id', INTEGER(), nullable=False)
goal = Column('ambassador_goal', INTEGER())
class Campaign(db.Model):
__tablename__ = 'campaign'
id = Column('id', INTEGER(), primary_key=True)
title = Column('title', TEXT(), nullable=False)
#ambassadors
id = Column('id', INTEGER(), primary_key=True)
title = Column('title', TEXT(), nullable=False)
active = Column('active', Boolean(), nullable=False, default=True)
goal = Column('goal', INTEGER(), default=0)
archived = Column('archived', Boolean(), default=False)
def get_donations(self):
from app.main.models import Donation
return Donation.query.filter_by(campaign_id=self.id).all()
def get_amount_raised(self):
donations = self.get_donations()
total = 0
for donation in donations:
total += donation.amount
return total
def get_ambassadors(self):
return AmbassadorMap.query.filter_by(campaign_id=self.id).all()
def __repr__(self) -> str:
return f"{self.id} - {self.title}"

View file

@ -1,13 +1,36 @@
from app import db
from app.campaigns import campaigns
from app.users.models import User
# from forms import LoginForm, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm
from app.campaigns.models import Campaign
from app.campaigns.forms import CreateCampaignForm
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user
from werkzeug.security import check_password_hash, generate_password_hash
from datetime import datetime
import os
from time import sleep
@campaigns.route('add_campaign')
@campaigns.route('add_campaign', methods=['GET', 'POST'])
def add_campaign():
return render_template('add_campaign.html')
form = CreateCampaignForm()
if form.validate_on_submit():
campaign = Campaign(
title=form.title.data,
active=form.active.data,
goal=form.goal.data
)
db.session.add(campaign)
db.session.commit()
return redirect(url_for('admin.administration'))
return render_template('add_campaign.html',
form=form)
@campaigns.route('campaign_page/<campaign_id>')
def campaign_page(campaign_id):
campaign = Campaign.query.filter_by(id=campaign_id).first()
return render_template('campaign_page.html',
campaign=campaign)
@campaigns.route('add_ambassador')
def add_ambassador():
return render_template('add_ambassador.html')

View file

@ -1,2 +1,56 @@
{% extends 'base.html' %}
{% block title %}Add campaign{% endblock title %}
{% block content %}
<main class="container-lg mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title">Register</h3>
<form method="post" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.title.label(class="form-label") }}
{{ form.title(class="form-control") }}
{% if form.title.errors %}
<ul class="errors">
{% for error in form.title.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="mb-3">
{{ form.active.label(class="form-label") }}
{{ form.active() }}
{% if form.active.errors %}
<ul class="errors">
{% for error in form.active.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="mb-3">
{{ form.goal.label(class="form-label") }}
{{ form.goal(class="form-control") }}
{% if form.goal.errors %}
<ul class="errors">
{% for error in form.goal.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{{ form.submit(class='btn btn-primary') }}
</form>
</div>
</div>
</div>
</div>
</main>
{% endblock content %}

View file

@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% block title %}{{campaign.title}}{% endblock title %}
{% block content %}
<main class="container-lg mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card mb-5">
<div class="card-body">
<div class="d-flex justify-content-center align-items-center">
<h2 class="text-center">{{campaign.title}} Ambassadors</h2>
<a href="{{url_for('campaigns.add_ambassador')}}" class="btn btn-primary">Become an ambassador!</a>
</div>
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>Name</th>
<th>Goal</th>
<th>Ambassador Page Link</th>
</tr>
</thead>
<tbody>
{% for campaign_ambassador in campaign.get_ambassadors() %}
<tr>
<td>{{campaign_ambassador.user_id}}</td>
<td>{{campaign_ambassador.goal}}</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-body">
<h2 class="text-center">{{campaign.title}}</h2>
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Amount</th>
<th>User</th>
</tr>
</thead>
<tbody>
{% for campaign_donation in campaign.get_donations() %}
<tr>
<td>{{campaign_donation.id}}</td>
<td>{{campaign_donation.amount}}</td>
<td>{{campaign_donation.get_user()}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
{% endblock content %}

23
app/main/models.py Normal file
View file

@ -0,0 +1,23 @@
from app import db
from flask import current_app
from flask_login import UserMixin, current_user
from sqlalchemy import TEXT, Column, Boolean, ForeignKey, TEXT, INTEGER, VARCHAR
import jwt
from datetime import datetime, timezone, timedelta
class Donation(db.Model):
__tablename__ = 'donation'
id = Column('id', INTEGER(), primary_key=True, autoincrement=True)
currency_type = Column('currency_type', TEXT(), nullable=False)
amount = Column('amount', INTEGER(), nullable=False)
user_id = Column('user_id', INTEGER(), nullable=False)
campaign_id = Column('campaign_id', INTEGER(), nullable=False)
anonymous = Column('anonymous', Boolean(), default=False)
def get_user(self):
from app.users.models import User
return User.query.filter_by(id=self.user_id).first()
def __repr__(self) -> str:
return f"{self.id} - {self.currency_type} - {self.amount}"

View file

@ -1,6 +1,7 @@
from app import db
from app.main import main
from app.users.models import User
from app.campaigns.models import Campaign
# from forms import LoginForm, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user
@ -13,5 +14,6 @@ import os
@main.route('/homepage')
def homepage():
return render_template('homepage.html')
active_campaigns = Campaign.query.filter_by(active=True).all()
return render_template('homepage.html',
active_campaigns=active_campaigns)

0
app/main/static/main.css Normal file
View file

View file

@ -14,18 +14,19 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a href="{{url_for('main.homepage')}}" style="text-decoration: none; margin-right: 10px;"><p>Pilzno</p></a>
{% if current_user.is_authenticated %}
<a href="{{url_for('users.user_page', user_id=current_user.id)}}" style="text-decoration: none;"><span class="navbar-brand">User Page</span></a>
{% if current_user.user_type == 'Admin' %}
<a href="{{url_for('admin.administration')}}" style="text-decoration: none;"><span class="navbar-brand">Admin Management</span></a>
{% endif %}
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="{{url_for('users.logout')}}">Logout</a>
</div>
</div>
<a href="{{url_for('users.user_page', user_id=current_user.id)}}" style="text-decoration: none;"><span class="navbar-brand">User Page</span></a>
{% if current_user.user_type == 'Admin' %}
<a href="{{url_for('admin.administration')}}" style="text-decoration: none;"><span class="navbar-brand">Admin Management</span></a>
<a href="{{url_for('admin.archive')}}" style="text-decoration: none;"><span class="navbar-brand">Archive</span></a>
{% endif %}
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="{{url_for('users.logout')}}">Logout</a>
</div>
</div>
{% else %}
<a href="{{url_for('users.login')}}" style="text-decoration: none;"><span class="navbar-brand">Login</span></a>
<a href="{{url_for('users.register_user')}}" style="text-decoration: none;"><span class="navbar-brand">Register</span></a>
<a href="{{url_for('users.login')}}" style="text-decoration: none;"><span class="navbar-brand">Login</span></a>
<a href="{{url_for('users.register_user')}}" style="text-decoration: none;"><span class="navbar-brand">Register</span></a>
{% endif %}
</nav>
{% with messages = get_flashed_messages(with_categories=true) %}

View file

@ -1,2 +1,44 @@
{% extends 'base.html' %}
{% block title %}Customer Search{% endblock title %}
{% block title %}Homepage{% endblock title %}
{% block stylesheet %}
<style>
.custom-table {
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden; /* Ensures rounded corners apply to the entire table */
}
</style>
{% endblock stylesheet %}
{% block content %}
<main>
<div class="container my-4 px-5">
<div class="text-center mb-4">
<a href="{{ url_for('arba_yesodot.arba_yesodot_homepage') }}" class="btn btn-primary">Check out the arba yesodot program!</a>
</div>
<h2 class="text-center">Visit Campaigns</h2>
<table class="table table-bordered table-striped custom-table">
<thead class="table-dark">
<tr>
<th>Title</th>
<th>Goal</th>
<th>Raised</th>
<th>Campaign Link</th>
</tr>
</thead>
<tbody>
{% for campaign in active_campaigns %}
<tr>
<td>{{campaign.title}}</td>
<td>{{campaign.goal}}</td>
<td>{{campaign.get_amount_raised()}}</td>
<td><a href="{{url_for('campaigns.campaign_page', campaign_id=campaign.id)}}">See campaign details</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</main>
{% endblock content %}

View file

@ -1,7 +1,7 @@
from app import db
from flask import current_app
from flask_login import UserMixin, current_user
from sqlalchemy import TEXT, Column, Boolean, ForeignKey, TEXT, INTEGER, VARCHAR
from sqlalchemy import TEXT, Column, Boolean, ForeignKey, TEXT, INTEGER, VARCHAR, select
import jwt
from datetime import datetime, timezone, timedelta
@ -15,6 +15,14 @@ class User(db.Model, UserMixin):
password = Column('password', TEXT(), nullable=False)
user_type = Column('user_type', TEXT(), nullable=False)
def get_donations(self):
from app.main.models import Donation
return db.session.execute(select(Donation.currency_type,Donation.amount).where(Donation.user_id == self.id)).all()
def __repr__(self) -> str:
return f"{self.first_name} {self.last_name}"
# donation_id = Column(INTEGER, ForeignKey('donation.id'))
def get_reset_token(self, expiration=600):
reset_token = jwt.encode(
{

View file

@ -1,6 +1,8 @@
from app import db
from app.users import users
from app.users.models import User
from app.campaigns.models import Campaign
from app.main.models import Donation
from app.users.forms import LoginForm, RegisterUserForm#, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user
@ -15,17 +17,19 @@ import os
@login_required
def user_page(user_id):
if not int(current_user.id) == int(user_id):
return redirect(url_for('main.dashboard'))
return redirect(url_for('main.homepage'))
user = User.query.filter_by(id=user_id).first()
donations = user.get_donations()
return render_template('user_page.html',
user=user)
user=user,
donations=donations)
@users.route('/')
@users.route('/login', methods=('GET', 'POST'))
def login():
if current_user.is_authenticated:
return redirect(url_for('main.dashboard'))
return redirect(url_for('main.homepage'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()

View file

@ -1,38 +1,40 @@
{% extends 'base.html' %}
{% block title %}User Page{% endblock title %}
{% block content %}
<main class="container">
<main class="container-lg mt-5">
<div class="row mt-4">
<div class="col-md-6 offset-md-3">
<h1>Welcome, {{ user.first_name }} {{ user.last_name }}</h1>
<hr>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card mb-5">
<div class="card-body">
<div class="d-flex justify-content-center align-items-center">
<h2 class="text-center">Donations</h2>
</div>
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>Currency</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{% for donation in donations %}
<tr>
<td>{{donation[0]}}</td>
<td>{{donation[1]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
<div class="col-md-10 offset-md-1">
{% if user_reports %}
<h2>Downloadable Files:</h2>
<table class="table mt-3">
<thead>
<tr>
<th>Report Type</th>
<th>Number of Rows</th>
<th>Time Created</th>
<th>Range Start</th>
<th>Range End</th>
<th>Download</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% else %}
<p>No reports available.</p>
{% endif %}
</div>
</div>
</div>
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,9 @@
from flask import Blueprint
users_api = Blueprint('users_api',
__name__,
template_folder='templates',
static_folder='static',
url_prefix='/users_api')
from app.users.users_api import routes

View file

@ -0,0 +1,18 @@
from app import db
from flask import render_template
from app.users.users_api import users_api
from app.users.models import User
from time import sleep
@users_api.route('update_admin_status/<id>/<status>', methods=['PUT'])
def update_admin_status(id, status):
if status == 'true':
status = 'Admin'
else:
status = 'User'
User.query.filter_by(id=id).update({'user_type':status})
db.session.commit()
sleep(1)
return {'status':'success'}

View file

@ -0,0 +1,151 @@
from sqlalchemy import create_engine, MetaData, Table, select, insert, func, update, bindparam, delete
import os
import csv
from datetime import datetime, timedelta
from dateutil.parser import parse
import time
import json
from werkzeug.security import generate_password_hash
import random
def engineer():
path1 = 'C:/Users/Lenovo/Desktop/Pilzno/instance/site.db'
path2 = '/home/yisroel2/Desktop/Pilzno/instance/site.db'
path3 = '/home/ubuntu/PilznoProject/PilznoProduction/instance/site.db'
for p in [path1, path2, path3]:
if os.path.exists(p):
path = p
break
engine = create_engine(f'sqlite:///{path}')
metadata_obj = MetaData()
metadata_obj.reflect(bind=engine)
return engine, metadata_obj
def check_db():
engine, metadata_obj = engineer()
user_table = Table("user", metadata_obj, autoload_with=engine)
donation_table = Table("donation", metadata_obj, autoload_with=engine)
campaign_table = Table("campaign", metadata_obj, autoload_with=engine)
with engine.connect() as conn:
print('USERS')
users = conn.execute(select(user_table)).all()
if users:
for user in users:
print(user.first_name)
else:
print('no users')
print("DONATIONS")
donations = conn.execute(select(donation_table)).all()
if donations:
for donation in donations:
print(donation.id)
else:
print('no donations')
campaigns = conn.execute(select(campaign_table)).all()
print('CAMPAIGNS')
if campaigns:
for campaign in campaigns:
print(campaign.title)
else:
print('no campaigns')
def insert_users():
users = [
['Yisroel', 'Baum', 'yisroel.d.baum@gmail.com', generate_password_hash('12'), 'Admin'],
['Yoni', 'Gerzi', 'yoni@gerzi.com', generate_password_hash('12'), 'User'],
['Shmuli', 'Modes', 'shmuli@modes.com', generate_password_hash('12'), 'User'],
['Emma', 'Baum', 'emma@baum.com', generate_password_hash('12'), 'User'],
['Yisroel', 'Factor', 'yisroel@factor.com', generate_password_hash('12'), 'User'],
['Yaakov', 'Frager', 'yaakov@frager.com', generate_password_hash('12'), 'User'],
['Michael', 'Oshman', 'michael@oshman.com', generate_password_hash('12'), 'User'],
['Shalom', 'Goldberg', 'shalom@goldberg.com', generate_password_hash('12'), 'User'],
['Daniel', 'Caller', 'daniel@caller.com', generate_password_hash('12'), 'User'],
]
engine, metadata_obj = engineer()
user_table = Table("user", metadata_obj, autoload_with=engine)
with engine.connect() as conn:
for user in users:
conn.execute(user_table.insert().values(
first_name = user[0],
last_name = user[1],
email = user[2],
password = user[3],
user_type = user[4]
))
conn.commit()
def insert_donations():
engine, metadata_obj = engineer()
donation_table = Table("donation", metadata_obj, autoload_with=engine)
currency_types = ['shekel', 'dollar']
boolean_choice = [True, False]
with engine.connect() as conn:
for _ in range(100):
conn.execute(donation_table.insert().values(
currency_type = currency_types[random.randint(0,1)],
amount = random.randint(1,200),
anonymous = boolean_choice[random.randint(0,1)],
campaign_id=random.randint(1,4),
user_id=random.randint(1,9)
))
conn.commit()
def insert_campaigns():
engine, metadata_obj = engineer()
campaign_table = Table("campaign", metadata_obj, autoload_with=engine)
campaign_titles = ['general campaign', 'yomim noraim 2024', 'pesach kibbudim 2024', 'RH kibbudim 2024']
is_active = [True, False]
with engine.connect() as conn:
for title in campaign_titles:
conn.execute(campaign_table.insert().values(
title=title,
active=is_active[random.randint(0,1)],
goal=5000,
archived=False
))
conn.commit()
def insert_ambassador_relationships():
engine, metadata_obj = engineer()
user_table = Table("user", metadata_obj, autoload_with=engine)
donation_table = Table("donation", metadata_obj, autoload_with=engine)
campaign_table = Table("campaign", metadata_obj, autoload_with=engine)
with engine.connect() as conn:
campaign_ids = conn.execute(select(campaign_table.c.id)).all()
user_ids = conn.execute(select(user_table.c.id)).all()
def test_selections():
engine, metadata_obj = engineer()
user_table = Table("user", metadata_obj, autoload_with=engine)
donation_table = Table("donation", metadata_obj, autoload_with=engine)
campaign_table = Table("campaign", metadata_obj, autoload_with=engine)
def delete_all():
engine, metadata_obj = engineer()
user_table = Table("user", metadata_obj, autoload_with=engine)
donation_table = Table("donation", metadata_obj, autoload_with=engine)
campaign_table = Table("campaign", metadata_obj, autoload_with=engine)
with engine.connect() as conn:
conn.execute(delete(user_table))
conn.execute(delete(campaign_table))
conn.execute(delete(donation_table))
conn.commit()
def insert_all():
insert_users()
insert_campaigns()
insert_donations()
if __name__ == '__main__':
delete_all()
insert_all()

39
requirements.txt Normal file
View file

@ -0,0 +1,39 @@
alembic==1.13.2
amqp==5.2.0
billiard==4.2.0
blinker==1.8.2
celery==5.4.0
cffi==1.17.0
click==8.1.7
click-didyoumean==0.3.1
click-plugins==1.1.1
click-repl==0.3.0
colorama==0.4.6
cryptography==43.0.1
dnspython==2.6.1
email_validator==2.2.0
Flask==3.0.3
Flask-Login==0.6.3
Flask-Mail==0.10.0
Flask-Migrate==4.0.7
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.1
greenlet==3.0.3
idna==3.8
itsdangerous==2.2.0
Jinja2==3.1.4
jwt==1.3.1
kombu==5.4.0
Mako==1.3.5
MarkupSafe==2.1.5
prompt_toolkit==3.0.47
pycparser==2.22
python-dateutil==2.9.0.post0
six==1.16.0
SQLAlchemy==2.0.33
typing_extensions==4.12.2
tzdata==2024.1
vine==5.1.0
wcwidth==0.2.13
Werkzeug==3.0.4
WTForms==3.1.2