Compare commits

..

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__
**/__pycache__
/errorlog.txt /errorlog.txt
/instance /instance
/logs.log /logs.log

View file

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

View file

@ -1,6 +1,8 @@
from app import db from app import db
from app.admin import admin from app.admin import admin
from app.users.models import User 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 app.users.forms import LoginForm, RegisterUserForm#, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm
from flask import render_template, redirect, url_for, flash, request from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user from flask_login import login_required, login_user, current_user, logout_user
@ -10,4 +12,14 @@ import os
@admin.route('administration') @admin.route('administration')
def 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' %} {% extends 'base.html' %}
{% block title %}Administration{% endblock title %} {% 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 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): class Campaign(db.Model):
__tablename__ = 'campaign' __tablename__ = 'campaign'
id = Column('id', INTEGER(), primary_key=True) id = Column('id', INTEGER(), primary_key=True)
title = Column('title', TEXT(), nullable=False) title = Column('title', TEXT(), nullable=False)
#ambassadors 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 import db
from app.campaigns import campaigns from app.campaigns import campaigns
from app.users.models import User 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 import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user from flask_login import login_required, login_user, current_user, logout_user
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
from datetime import datetime from datetime import datetime
import os import os
from time import sleep
@campaigns.route('add_campaign') @campaigns.route('add_campaign', methods=['GET', 'POST'])
def add_campaign(): 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' %} {% extends 'base.html' %}
{% block title %}Add campaign{% endblock title %} {% 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 import db
from app.main import main from app.main import main
from app.users.models import User from app.users.models import User
from app.campaigns.models import Campaign
# from forms import LoginForm, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm # from forms import LoginForm, RequestResetForm, ResetPasswordForm, EditUserForm, AddUserForm
from flask import render_template, redirect, url_for, flash, request from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, login_user, current_user, logout_user from flask_login import login_required, login_user, current_user, logout_user
@ -13,5 +14,6 @@ import os
@main.route('/homepage') @main.route('/homepage')
def homepage(): def homepage():
active_campaigns = Campaign.query.filter_by(active=True).all()
return render_template('homepage.html') 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"> <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> <a href="{{url_for('main.homepage')}}" style="text-decoration: none; margin-right: 10px;"><p>Pilzno</p></a>
{% if current_user.is_authenticated %} {% 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> <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' %} {% 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.administration')}}" style="text-decoration: none;"><span class="navbar-brand">Admin Management</span></a>
{% endif %} <a href="{{url_for('admin.archive')}}" style="text-decoration: none;"><span class="navbar-brand">Archive</span></a>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup"> {% endif %}
<div class="navbar-nav"> <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<a class="nav-item nav-link active" href="{{url_for('users.logout')}}">Logout</a> <div class="navbar-nav">
</div> <a class="nav-item nav-link active" href="{{url_for('users.logout')}}">Logout</a>
</div> </div>
</div>
{% else %} {% else %}
<a href="{{url_for('users.login')}}" style="text-decoration: none;"><span class="navbar-brand">Login</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> <a href="{{url_for('users.register_user')}}" style="text-decoration: none;"><span class="navbar-brand">Register</span></a>
{% endif %} {% endif %}
</nav> </nav>
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}

View file

@ -1,2 +1,44 @@
{% extends 'base.html' %} {% 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 app import db
from flask import current_app from flask import current_app
from flask_login import UserMixin, current_user 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 import jwt
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
@ -15,6 +15,14 @@ class User(db.Model, UserMixin):
password = Column('password', TEXT(), nullable=False) password = Column('password', TEXT(), nullable=False)
user_type = Column('user_type', 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): def get_reset_token(self, expiration=600):
reset_token = jwt.encode( reset_token = jwt.encode(
{ {

View file

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

View file

@ -1,38 +1,40 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}User Page{% endblock title %} {% block title %}User Page{% endblock title %}
{% block content %} {% block content %}
<main class="container"> <main class="container-lg mt-5">
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
<h1>Welcome, {{ user.first_name }} {{ user.last_name }}</h1> <h1>Welcome, {{ user.first_name }} {{ user.last_name }}</h1>
<hr> <hr>
</div> </div>
</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>
<div class="col-md-10 offset-md-1"> </div>
{% 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> </main>
{% endblock %} {% 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