diff --git a/app/__init__.py b/app/__init__.py index 44fd355..a58ce1b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -37,6 +37,9 @@ app.register_blueprint(vmanager_blueprint) from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') +from .settings import settings as settings_blueprint +app.register_blueprint(settings_blueprint, url_prefix='/settings') + from .uinvoice import uinvoice as uinvoice_blueprint app.register_blueprint(uinvoice_blueprint, url_prefix='/uinvoice') @@ -57,7 +60,7 @@ app.json_encoder = CustomJSONEncoder if not app.debug: import logging from logging.handlers import RotatingFileHandler - file_handler = RotatingFileHandler('/home/proxadmin/appserver/proxadmin/app/log/proxadmin.log', 'a', 1 * 1024 * 1024, 10) + file_handler = RotatingFileHandler('/home/proxadmin/appserver/proxadmin/log/proxadmin.log', 'a', 1 * 1024 * 1024, 10) file_handler.setLevel(logging.INFO) file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) app.logger.addHandler(file_handler) diff --git a/app/auth/routes.py b/app/auth/routes.py index 118d50b..6d35341 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -56,6 +56,43 @@ def login(): return render_template('auth/login.html', page=page, form=form) +#PROFILE +@auth.route('/profile', methods=['GET', 'POST']) +@login_required +def profile(): + page = { 'title': 'Edit Profile' } + + currentmail = current_user.email + ouruser = User.query.filter_by(email=currentmail).first() + db.session.commit() + wallet = "%.2f" % round(ouruser.wallet, 3) + print(wallet) + form = EditProfileForm() + if form.validate_on_submit(): + current_user.name = form.name.data + current_user.address = form.address.data + current_user.city = form.city.data + current_user.postcode = form.postcode.data + current_user.country = form.country.data + current_user.phone = form.phone.data + current_user.org_responsible = form.org_responsible.data + current_user.org_bulstat = form.org_bulstat.data + current_user.twofactor = form.twofactor.data + db.session.add(current_user) + db.session.commit() + flash('Info Updated!') + + form.twofactor.data = current_user.twofactor + form.name.data = current_user.name + form.address.data = current_user.address + form.city.data = current_user.city + form.postcode.data = current_user.postcode + form.country.data = current_user.country + form.phone.data = current_user.phone + form.org_responsible.data = current_user.org_responsible + form.org_bulstat.data = current_user.org_bulstat + + return render_template('auth/profile.html', page=page, form=form) @auth.route('/twofactor', methods=['GET', 'POST']) def twofactor(): diff --git a/app/models.py b/app/models.py index 7e720f5..b2fa0bc 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,3 @@ -import hashlib from werkzeug.security import generate_password_hash, check_password_hash from itsdangerous import TimedJSONWebSignatureSerializer as Serializer @@ -9,12 +8,13 @@ from . import db, lm import os import base64 +import hashlib +from decimal import Decimal +from datetime import date, time, datetime, timedelta import json +from sortedcontainers import SortedDict import requests import onetimepass -from datetime import date, datetime, time, timedelta -from sortedcontainers import SortedDict - class Permission: DEPLOY = 0x01 @@ -57,6 +57,7 @@ class User(db.Model, UserMixin): password_hash = db.Column(db.String(128)) confirmed = db.Column(db.Boolean, default=False) + active = db.Column(db.Boolean, default=True) member_since = db.Column(db.DateTime(), default=datetime.utcnow) last_seen = db.Column(db.DateTime(), default=datetime.utcnow) last_ip = db.Column(db.String(128)) @@ -70,15 +71,24 @@ class User(db.Model, UserMixin): postcode = db.Column(db.String(10)) country = db.Column(db.String(64)) phone = db.Column(db.String(64)) + org_companyname = db.Column(db.Unicode(64)) + org_regaddress = db.Column(db.Unicode(128)) org_responsible = db.Column(db.Unicode(128)) org_bulstat = db.Column(db.String(16)) + org_vat = db.Column(db.Boolean, default=False) + org_vatnum = db.Column(db.String(16)) + + wallet = db.Column(db.Float) + credit = db.Column(db.Float) + creditlimit = db.Column(db.Float, default=20.0) + + currency = db.Column(db.String(3), default='BGN') + language = db.Column(db.String(2), default='BG') inv_deployments = db.relationship('Deployment', backref='owner', lazy='dynamic') inv_contracts = db.relationship('Contract', backref='owner', lazy='dynamic') inv_domains = db.relationship('Domain', backref='owner', lazy='dynamic') - currency = db.Column(db.String(3), default='BGN') - def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.role is None: @@ -271,7 +281,7 @@ class Service(db.Model): pid = db.Column(db.Integer, primary_key=True) #PK name = db.Column(db.String(64)) image = db.Column(db.String(128)) - description = db.Column(db.String(128)) + description = db.Column(db.Unicode(128)) unitprice = db.Column(db.Float) enabled = db.Column(db.Boolean) @@ -305,17 +315,57 @@ class Service(db.Model): } return services -class Order(db.Model): - __tablename__ = 'orders' +#TRANSACTIONS +class Transaction(db.Model): + __tablename__ = 'transaction' pid = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow) - units = db.Column(db.Integer, default=1) - unitvalue = db.Column(db.Float) currency = db.Column(db.String, default='BGN') - paid = db.Column(db.Boolean, default=False) -#class Invoice(db.Model) +#PROFORMA INVOICE CLASS +class Invoice(db.Model): + __tablename__ = 'invoice' + pid = db.Column(db.Integer, primary_key=True) + invoice_number = db.Column(db.Integer, unique=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.pid')) #FK + date_created = db.Column(db.DateTime, index=True, default=datetime.utcnow) + currency = db.Column(db.String, default='BGN') + tax = db.Column(db.Float) #VAT + description = db.Column(db.Unicode(255)) + + def sub_total(self): + items = self.items + sub_total = 0 + for item in items: + sub_total += item.total() + return sub_total + + def tax_amount(self): + if not self.tax: + return 0 + return self.sub_total() * self.tax / 100.0 + + def grand_total(self): + amount_str = str(self.sub_total() + self.tax_amount()) + amount = Decimal(amount_str) + rounder = Decimal("0.05") # precision for rounding + return amount - amount.remainder_near(rounder) + +class InvoiceItem(db.Model): + __tablename__ = 'invoice_item' + pid = db.Column(db.Integer, primary_key=True) + item_number = db.Column(db.Integer) + invoice_id = db.Column(db.Integer, db.ForeignKey('invoice.pid')) #FK + item_title = db.Column(db.Unicode(255)) + item_quantity = db.Column(db.Float) + item_price = db.Column(db.Float) + amount = db.Column(db.Float) + + invoice = db.relationship(Invoice, backref=backref('items', order_by=item_number, cascade="delete")) + + def total(self): + return self.item_quantity * self.item_price #INVENTORY CLASSES class Deployment(db.Model): @@ -328,35 +378,34 @@ class Deployment(db.Model): enabled = db.Column(db.Boolean) machine_id = db.Column(db.BigInteger) #cubeid - machine_alias = db.Column(db.String) #dns name for easy managing + machine_alias = db.Column(db.String) #dns name machine_cpu = db.Column(db.Integer) machine_mem = db.Column(db.Integer) machine_hdd = db.Column(db.Integer) - credit = db.Column(db.Float) def charge(): result = Deployment.query.all() for deploy in result: - managed_user = User.query.get(deploy.user_id) - db.session.add(deploy) - if datetime.utcnow.date() > (deploy.date_expire - timedelta(days=10)): - if deploy.credit > 0: - print('{} Deployment {} will expire in 10 days at {}. Creating new order...'.format(managed_user.email, deploy.machine_alias, deploy.date_expire)) - current_product = Product.query.get(int(deploy.product_id)) - order = Order(user_id=managed_user.pid, unitvalue=deploy.credit, description='Cloud server {} - {}'.format(current_product.name, deploy.machine_alias)) - db.session.add(order) - deploy.credit = 0 - deploy.data_expire = datetime.utcnow.date() + timedelta(days=30) - else: + if deploy.enabled == True: + managed_user = User.query.get(deploy.user_id) + db.session.add(managed_user) + current_product = Product.query.get(int(deploy.product_id)) cpu_cost = deploy.machine_cpu * current_app.config['CPU_RATIO'] mem_cost = ( deploy.machine_mem / 1024 ) * current_app.config['MEM_RATIO'] hdd_cost = deploy.machine_hdd * current_app.config['HDD_RATIO'] total = cpu_cost + mem_cost + hdd_cost - if deploy.enabled == True: - print('{}> Charging deployment #{} with {} ({} monthly)'.format(managed_user.email, deploy.pid, total)) - deploy.credit += total - db.session.commit() - return True + + + + if managed_user.wallet - managed_user.credit > managed_user.creditlimit: + print('{}> Deployment #{} costs {} today. Credit is now {}'.format(managed_user.email, deploy.machine_id, total, managed_user.credit)) + managed_user.credit += total + else: + print('{}> Deployment #{} costs {} today. Credit now is {} and its lower than the credit limit {}.'.format(managed_user.email, deploy.machine_id, total, managed_user.credit, managed_user.creditlimit) + print('') + if datetime.utcnow.date() == + db.session.commit() + if deploy. class Contract(db.Model): __tablename__ = 'contracts' @@ -367,7 +416,7 @@ class Contract(db.Model): date_expire = db.Column(db.DateTime) enabled = db.Column(db.Boolean) - description = db.Column(db.String) + description = db.Column(db.Unicode) units = db.Column(db.Integer) discount = db.Column(db.Integer) #percent credit = db.Column(db.Float) diff --git a/app/settings/__init__.py b/app/settings/__init__.py new file mode 100644 index 0000000..a90b227 --- /dev/null +++ b/app/settings/__init__.py @@ -0,0 +1,3 @@ +from flask import Blueprint +uinvoice = Blueprint('settings', __name__) +from . import routes diff --git a/app/settings/forms.py b/app/settings/forms.py new file mode 100644 index 0000000..9efb0b3 --- /dev/null +++ b/app/settings/forms.py @@ -0,0 +1,60 @@ +from iso3166 import countries +import string +import random +from ..models import User, Role + +from flask_wtf import FlaskForm, RecaptchaField +from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectField, DecimalField +from wtforms import validators, ValidationError +from wtforms.fields.html5 import EmailField + +class EditProfileForm(FlaskForm): + name = StringField('Лице за контакт:', [validators.DataRequired(), validators.Length(3, 60)]) + address = StringField('Адрес:', [validators.DataRequired(), validators.Length(2, 50)]) + city = StringField('Град:', [validators.DataRequired(), validators.Length(2,40)]) + + postcode = StringField('Пощенски Код:') + + clist = [] + for c in countries: + clist.append((c.alpha2, c.name)) + country = SelectField('Държава:', choices=clist, default='BG') + + phone = StringField('Телефон:') + org_responsible = StringField('Отговорно Лице:') + org_bulstat = StringField('БУЛСТАТ:') + twofactor = BooleanField('2-factor authentication') + submit = SubmitField('Обнови') + + +class EditProfileAdminForm(FlaskForm): + email = StringField('Електроннa поща (логин):', [validators.DataRequired(), validators.Length(1, 64), validators.Email()]) + confirmed = BooleanField('Активиран') + role = SelectField('Роля', coerce=int) + + name = StringField('Лице за контакт:', [validators.DataRequired(), validators.Length(3, 60)]) + address = StringField('Адрес:', [validators.DataRequired(), validators.Length(2, 50)]) + city = StringField('Град:', [validators.DataRequired(), validators.Length(2,40)]) + postcode = DecimalField('Пощенски Код:') + + clist = [] + for c in countries: + clist.append((c.alpha2, c.name)) + country = SelectField('Държава:', choices=clist) + + phone = DecimalField('Телефон:', [validators.DataRequired()]) + org_responsible = StringField('Отговорно Лице:') + org_bulstat = StringField('БУЛСТАТ:') + submit = SubmitField('Обнови') + + def __init__(self, user, *args, **kwargs): + super(EditProfileAdminForm, self).__init__(*args, **kwargs) + self.role.choices = [(role.pid, role.name) + for role in Role.query.order_by(Role.name).all()] + self.user = user + + def validate_email(self, field): + if field.data != self.user.email and User.query.filter_by(email=field.data).first(): + raise ValidationError('Email-а е вече регистриран.') + + diff --git a/app/settings/routes.py b/app/settings/routes.py new file mode 100644 index 0000000..6b293ed --- /dev/null +++ b/app/settings/routes.py @@ -0,0 +1,51 @@ +from flask import render_template, redirect, request, url_for, flash, session, abort, current_app +from flask_login import login_required, login_user, logout_user, current_user +from sqlalchemy import desc + +from . import settings +from .forms import EditProfileForm, EditProfileAdminForm, ChargeForm, PaymentForm + +from ..email import send_email +from .. import db +from ..models import User, Order + +#PROFILE +@settings.route('/profile', methods=['GET', 'POST']) +@login_required +def profile(): + page = { 'title': 'Edit Profile' } + + currentmail = current_user.email + ouruser = User.query.filter_by(email=currentmail).first() + db.session.commit() + + #wallet = "%.2f" % round(ouruser.wallet, 3) + #print(wallet) + + form = EditProfileForm() + if form.validate_on_submit(): + current_user.name = form.name.data + current_user.address = form.address.data + current_user.city = form.city.data + current_user.postcode = form.postcode.data + current_user.country = form.country.data + current_user.phone = form.phone.data + current_user.org_responsible = form.org_responsible.data + current_user.org_bulstat = form.org_bulstat.data + current_user.twofactor = form.twofactor.data + db.session.add(current_user) + db.session.commit() + flash('Info Updated!') + + form.twofactor.data = current_user.twofactor + form.name.data = current_user.name + form.address.data = current_user.address + form.city.data = current_user.city + form.postcode.data = current_user.postcode + form.country.data = current_user.country + form.phone.data = current_user.phone + form.org_responsible.data = current_user.org_responsible + form.org_bulstat.data = current_user.org_bulstat + + return render_template('settings/profile.html', page=page, form=form) + diff --git a/app/templates/nav.html b/app/templates/nav.html index b47d56a..27bcc43 100644 --- a/app/templates/nav.html +++ b/app/templates/nav.html @@ -47,7 +47,7 @@
  • Dashboard
  • Orders
  • -
  • Profile
  • +
  • Profile
  • Logout
  • diff --git a/app/templates/settings/_sidebar.html b/app/templates/settings/_sidebar.html new file mode 100644 index 0000000..d3ce7eb --- /dev/null +++ b/app/templates/settings/_sidebar.html @@ -0,0 +1,10 @@ +
    +
    +
    {{ current_user.name }}
    +
    +
    + 2-Factor: {{ current_user.twofactor }}
    +
    +
    +
    + diff --git a/app/templates/uinvoice/profile.html b/app/templates/settings/profile.html similarity index 97% rename from app/templates/uinvoice/profile.html rename to app/templates/settings/profile.html index aef2e69..63bd539 100644 --- a/app/templates/uinvoice/profile.html +++ b/app/templates/settings/profile.html @@ -12,14 +12,14 @@
    {% block sidebar %} -{% include "uinvoice/_sidebar.html" %} +{% include "settings/_sidebar.html" %} {% endblock %}
    Данни на профила
    -
    +

    {{ form.name.label }}
    {{ form.name(size=42) }}
    {% for error in form.name.errors %} diff --git a/app/uinvoice/forms.py b/app/uinvoice/forms.py index 87f328c..dbf6490 100644 --- a/app/uinvoice/forms.py +++ b/app/uinvoice/forms.py @@ -1,4 +1,3 @@ -from iso3166 import countries import string import random from ..models import User, Role @@ -8,55 +7,6 @@ from wtforms import StringField, PasswordField, BooleanField, SubmitField, Selec from wtforms import validators, ValidationError from wtforms.fields.html5 import EmailField -class EditProfileForm(FlaskForm): - name = StringField('Лице за контакт:', [validators.DataRequired(), validators.Length(3, 60)]) - address = StringField('Адрес:', [validators.DataRequired(), validators.Length(2, 50)]) - city = StringField('Град:', [validators.DataRequired(), validators.Length(2,40)]) - - postcode = StringField('Пощенски Код:') - - clist = [] - for c in countries: - clist.append((c.alpha2, c.name)) - country = SelectField('Държава:', choices=clist, default='BG') - - phone = StringField('Телефон:') - org_responsible = StringField('Отговорно Лице:') - org_bulstat = StringField('БУЛСТАТ:') - twofactor = BooleanField('2-factor authentication') - submit = SubmitField('Обнови') - - -class EditProfileAdminForm(FlaskForm): - email = StringField('Електроннa поща (логин):', [validators.DataRequired(), validators.Length(1, 64), validators.Email()]) - confirmed = BooleanField('Активиран') - role = SelectField('Роля', coerce=int) - - name = StringField('Лице за контакт:', [validators.DataRequired(), validators.Length(3, 60)]) - address = StringField('Адрес:', [validators.DataRequired(), validators.Length(2, 50)]) - city = StringField('Град:', [validators.DataRequired(), validators.Length(2,40)]) - postcode = DecimalField('Пощенски Код:') - - clist = [] - for c in countries: - clist.append((c.alpha2, c.name)) - country = SelectField('Държава:', choices=clist) - - phone = DecimalField('Телефон:', [validators.DataRequired()]) - org_responsible = StringField('Отговорно Лице:') - org_bulstat = StringField('БУЛСТАТ:') - submit = SubmitField('Обнови') - - def __init__(self, user, *args, **kwargs): - super(EditProfileAdminForm, self).__init__(*args, **kwargs) - self.role.choices = [(role.pid, role.name) - for role in Role.query.order_by(Role.name).all()] - self.user = user - - def validate_email(self, field): - if field.data != self.user.email and User.query.filter_by(email=field.data).first(): - raise ValidationError('Email-а е вече регистриран.') - class ChargeForm(FlaskForm): invoice_amount = DecimalField('Стойност:', [validators.DataRequired(), validators.NumberRange(min=0, max=6)]) submit = SubmitField('Зареди') diff --git a/app/uinvoice/routes.py b/app/uinvoice/routes.py index 88ebc9b..6564dbe 100644 --- a/app/uinvoice/routes.py +++ b/app/uinvoice/routes.py @@ -9,46 +9,6 @@ from ..email import send_email from .. import db from ..models import User, Order -#PROFILE -@uinvoice.route('/profile', methods=['GET', 'POST']) -@login_required -def profile(): - page = { 'title': 'Edit Profile' } - - currentmail = current_user.email - ouruser = User.query.filter_by(email=currentmail).first() - db.session.commit() - - #wallet = "%.2f" % round(ouruser.wallet, 3) - #print(wallet) - - form = EditProfileForm() - if form.validate_on_submit(): - current_user.name = form.name.data - current_user.address = form.address.data - current_user.city = form.city.data - current_user.postcode = form.postcode.data - current_user.country = form.country.data - current_user.phone = form.phone.data - current_user.org_responsible = form.org_responsible.data - current_user.org_bulstat = form.org_bulstat.data - current_user.twofactor = form.twofactor.data - db.session.add(current_user) - db.session.commit() - flash('Info Updated!') - - form.twofactor.data = current_user.twofactor - form.name.data = current_user.name - form.address.data = current_user.address - form.city.data = current_user.city - form.postcode.data = current_user.postcode - form.country.data = current_user.country - form.phone.data = current_user.phone - form.org_responsible.data = current_user.org_responsible - form.org_bulstat.data = current_user.org_bulstat - - return render_template('uinvoice/profile.html', page=page, form=form) - #INVOICES #@uinvoice.route('/charge', methods=['GET', 'POST']) #@login_required