Compare commits
10 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
3d23aeb224 | |
|
|
9180946916 | |
|
|
5abef64c69 | |
|
|
1435f78530 | |
|
|
be51020e75 | |
|
|
71def5d8ae | |
|
|
ba6faaaf7e | |
|
|
89080b7c6b | |
|
|
ef2b2a072e | |
|
|
385f284047 |
|
|
@ -0,0 +1,82 @@
|
|||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
*.qbs.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Directories with generated files
|
||||
.moc/
|
||||
.obj/
|
||||
.pch/
|
||||
.rcc/
|
||||
.uic/
|
||||
/build*/
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(mysitenews VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Sql)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql)
|
||||
|
||||
set(PROJECT_SOURCES
|
||||
main.cpp
|
||||
widget.cpp
|
||||
widget.h
|
||||
auth.h auth.cpp
|
||||
)
|
||||
|
||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_executable(mysitenews
|
||||
MANUAL_FINALIZATION
|
||||
${PROJECT_SOURCES}
|
||||
auth.h auth.cpp
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
# set_property(TARGET mysitenews APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||
else()
|
||||
if(ANDROID)
|
||||
add_library(mysitenews SHARED
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||
else()
|
||||
add_executable(mysitenews
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(mysitenews PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Sql
|
||||
)
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
# If you are developing for iOS or macOS you should consider setting an
|
||||
# explicit, fixed bundle identifier manually though.
|
||||
if(${QT_VERSION} VERSION_LESS 6.1.0)
|
||||
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.mysitenews)
|
||||
endif()
|
||||
set_target_properties(mysitenews PROPERTIES
|
||||
${BUNDLE_ID_OPTION}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS mysitenews
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
qt_finalize_executable(mysitenews)
|
||||
endif()
|
||||
107
README.md
107
README.md
|
|
@ -1,2 +1,109 @@
|
|||
# mysitenews
|
||||
|
||||
Графическое приложение на Qt для заполнения таблицы `random_data` в PostgreSQL.
|
||||
|
||||
При запуске открывается окно подключения к БД. После успешного подключения
|
||||
появляется форма добавления записи с полями `name` и `text`, а также счётчик
|
||||
текущего количества записей в таблице.
|
||||
|
||||
## Возможности
|
||||
|
||||
- подключение к PostgreSQL (host, port, user, password);
|
||||
- сохранение последней успешной конфигурации подключения;
|
||||
- отображение количества записей в `public.random_data`;
|
||||
- добавление записей (`name`, `text`);
|
||||
- показ ошибки при неудачном подключении.
|
||||
|
||||
## Требования
|
||||
|
||||
- CMake 3.16+;
|
||||
- компилятор с поддержкой C++17;
|
||||
- Qt 5 или Qt 6 (модули `Widgets` и `Sql`);
|
||||
- драйвер PostgreSQL для Qt (`QPSQL`);
|
||||
- сервер PostgreSQL.
|
||||
|
||||
Проверить наличие драйвера можно так:
|
||||
|
||||
```bash
|
||||
ls /usr/lib/x86_64-linux-gnu/qt6/plugins/sqldrivers/
|
||||
```
|
||||
|
||||
В каталоге должен быть `libqsqlpsql.so` (для Qt 5 — `libqsqlpsql.so` в
|
||||
соответствующем каталоге плагинов). В Debian/Ubuntu драйвер ставится пакетом
|
||||
`libqt6sql6-psql` (для Qt 5 — `libqt5sql5-psql`).
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
./build/mysitenews
|
||||
```
|
||||
|
||||
При сборке из Qt Creator используется каталог `build/Desktop-Debug`.
|
||||
|
||||
## База данных
|
||||
|
||||
Приложение обращается к БД `cards` и таблице `public.random_data`.
|
||||
Ожидаемая структура таблицы:
|
||||
|
||||
```sql
|
||||
CREATE TABLE public.random_data (
|
||||
id serial PRIMARY KEY,
|
||||
name varchar(100) NOT NULL,
|
||||
text text NOT NULL,
|
||||
description text,
|
||||
created_at timestamp DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### Права пользователя
|
||||
|
||||
Пользователю БД нужны права на таблицу и на последовательность `id`:
|
||||
|
||||
```sql
|
||||
GRANT SELECT, INSERT ON public.random_data TO cards_user;
|
||||
GRANT USAGE, SELECT ON SEQUENCE public.random_data_id_seq TO cards_user;
|
||||
```
|
||||
|
||||
Без прав на последовательность вставка завершится ошибкой
|
||||
`permission denied for sequence random_data_id_seq`.
|
||||
|
||||
## Использование
|
||||
|
||||
1. Запустите приложение.
|
||||
2. Введите адрес сервера, порт (по умолчанию `5432`), имя пользователя и пароль.
|
||||
3. Нажмите **OK**. При ошибке подключения будет показано сообщение.
|
||||
4. Заполните поля `name` и `text` и нажмите **ADD**.
|
||||
|
||||
Поле `text` сохраняется как HTML (`QTextEdit::toHtml()`). Оба поля обязательны.
|
||||
Длина `name` ограничена таблицей — 100 символов.
|
||||
|
||||
## Конфигурация
|
||||
|
||||
После успешного подключения параметры сохраняются через `QSettings`
|
||||
(`QSettings("mysitenews", "mysitenews")`). В Linux файл находится по пути:
|
||||
|
||||
```
|
||||
~/.config/mysitenews/mysitenews.conf
|
||||
```
|
||||
|
||||
Сохраняются `db/host`, `db/port`, `db/database`, `db/user`, `db/password`.
|
||||
При следующем запуске поля формы заполняются этими значениями.
|
||||
|
||||
**Внимание:** пароль хранится в открытом виде. Не используйте этот способ
|
||||
для продакшн-окружений; предпочтительнее `~/.pgpass` или переменные окружения.
|
||||
|
||||
## Структура проекта
|
||||
|
||||
| Файл | Назначение |
|
||||
| --- | --- |
|
||||
| `main.cpp` | точка входа, связывает окно авторизации и рабочее окно |
|
||||
| `auth.h` / `auth.cpp` | окно подключения к БД, сохранение конфигурации |
|
||||
| `widget.h` / `widget.cpp` | форма добавления записей и подсчёт количества |
|
||||
| `CMakeLists.txt` | конфигурация сборки |
|
||||
|
||||
## Лицензия
|
||||
|
||||
Проект распространяется под GNU General Public License v3. Подробности — в
|
||||
файле [LICENSE](LICENSE).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
#include "auth.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
#include <QSqlError>
|
||||
|
||||
auth::auth(QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, _nicknameLabel(new QLabel("nickname:"))
|
||||
, _nicknameEdit(new QLineEdit())
|
||||
, _passwordLabel(new QLabel("passwprd:"))
|
||||
, _passwordEdit(new QLineEdit())
|
||||
, _addressLabel(new QLabel("address:"))
|
||||
, _addressEdit(new QLineEdit())
|
||||
, _portLabel(new QLabel("port:"))
|
||||
, _portEdit(new QLineEdit())
|
||||
, _okButton(new QPushButton("&OK"))
|
||||
, _quitButton(new QPushButton("&QUIT"))
|
||||
, _vbox(new QVBoxLayout(this))
|
||||
, _db(QSqlDatabase::addDatabase("QPSQL"))
|
||||
{
|
||||
_nicknameLabel->setAlignment (Qt::AlignHCenter);
|
||||
_passwordLabel->setAlignment (Qt::AlignHCenter);
|
||||
_addressLabel->setAlignment (Qt::AlignHCenter);
|
||||
_portLabel->setAlignment (Qt::AlignHCenter);
|
||||
_passwordEdit->setEchoMode (QLineEdit::Password);
|
||||
|
||||
QSettings settings ("mysitenews", "mysitenews");
|
||||
_nicknameEdit->setText (settings.value ("db/user").toString ());
|
||||
_passwordEdit->setText (settings.value ("db/password").toString ());
|
||||
_addressEdit->setText (settings.value ("db/host").toString ());
|
||||
_portEdit->setText (settings.value ("db/port", "5432").toString ());
|
||||
_vbox->addWidget (_nicknameLabel);
|
||||
_vbox->addWidget (_nicknameEdit);
|
||||
_vbox->addWidget (_passwordLabel);
|
||||
_vbox->addWidget (_passwordEdit);
|
||||
_vbox->addWidget (_addressLabel);
|
||||
_vbox->addWidget (_addressEdit);
|
||||
_vbox->addWidget (_portLabel);
|
||||
_vbox->addWidget (_portEdit);
|
||||
_vbox->addWidget (_okButton);
|
||||
_vbox->addWidget (_quitButton);
|
||||
connect (_okButton, &QPushButton::clicked, this, &auth::onAuth);
|
||||
connect (_quitButton, &QPushButton::clicked, this, &auth::close);
|
||||
setLayout (_vbox);
|
||||
}
|
||||
|
||||
void
|
||||
auth::onAuth ()
|
||||
{
|
||||
QSettings settings ("mysitenews", "mysitenews");
|
||||
_db.setHostName(_addressEdit->text());
|
||||
_db.setPort(_portEdit->text().toInt());
|
||||
_db.setDatabaseName(settings.value ("db/database", "cards").toString ());
|
||||
_db.setUserName(_nicknameEdit->text());
|
||||
_db.setPassword(_passwordEdit->text());
|
||||
if (!_db.open()) {
|
||||
QMessageBox::critical(this, tr("Ошибка подключения"),
|
||||
_db.lastError().text());
|
||||
return;
|
||||
}
|
||||
|
||||
settings.setValue ("db/host", _db.hostName ());
|
||||
settings.setValue ("db/port", _db.port ());
|
||||
settings.setValue ("db/database", _db.databaseName ());
|
||||
settings.setValue ("db/user", _db.userName ());
|
||||
settings.setValue ("db/password", _db.password ());
|
||||
|
||||
emit dbOpenned(_db);
|
||||
close();
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef AUTH_H
|
||||
#define AUTH_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSqlDatabase>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class auth : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit auth (QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
QLabel *_nicknameLabel = nullptr;
|
||||
QLineEdit *_nicknameEdit = nullptr;
|
||||
QLabel *_passwordLabel = nullptr;
|
||||
QLineEdit *_passwordEdit = nullptr;
|
||||
QLabel *_addressLabel = nullptr;
|
||||
QLineEdit *_addressEdit = nullptr;
|
||||
QLabel *_portLabel = nullptr;
|
||||
QLineEdit *_portEdit = nullptr;
|
||||
QPushButton *_okButton = nullptr;
|
||||
QPushButton *_quitButton = nullptr;
|
||||
QVBoxLayout *_vbox = nullptr;
|
||||
QSqlDatabase _db;
|
||||
public slots:
|
||||
void onAuth ();
|
||||
signals:
|
||||
void dbOpenned(const QSqlDatabase &);
|
||||
};
|
||||
|
||||
#endif // AUTH_H
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#include "auth.h"
|
||||
#include "widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
QApplication app (argc, argv);
|
||||
auth a;
|
||||
Widget w;
|
||||
QObject::connect(&a, &auth::dbOpenned, &w, &Widget::init);
|
||||
a.show();
|
||||
return app.exec ();
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#include "widget.h"
|
||||
#include <QSqlQuery>
|
||||
|
||||
Widget::Widget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _grid(new QGridLayout(this))
|
||||
, _countId(new QLabel("NULL"))
|
||||
, _nameLabel(new QLabel("name: "))
|
||||
, _nameEdit(new QLineEdit())
|
||||
, _textLabel(new QLabel("text:"))
|
||||
, _textEdit(new QTextEdit())
|
||||
, _addBtn(new QPushButton("&ADD"))
|
||||
{
|
||||
_grid->addWidget(_countId, 0, 0, Qt::AlignHCenter);
|
||||
_grid->addWidget(_nameLabel, 1, 0, Qt::AlignRight);
|
||||
_grid->addWidget(_nameEdit, 1, 1, Qt::AlignVCenter);
|
||||
_grid->addWidget(_textLabel, 2, 0, Qt::AlignRight);
|
||||
_grid->addWidget(_textEdit, 2, 1, Qt::AlignHCenter);
|
||||
_grid->addWidget(_addBtn, 3, 0, Qt::AlignHCenter);
|
||||
setLayout(_grid);
|
||||
|
||||
connect(_addBtn, &QPushButton::clicked, this, &Widget::addRecord);
|
||||
}
|
||||
|
||||
Widget::~Widget () {}
|
||||
|
||||
void Widget::init(const QSqlDatabase &db)
|
||||
{
|
||||
_db = db;
|
||||
if (!_db.isOpen()) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
updateCount();
|
||||
show();
|
||||
}
|
||||
|
||||
void Widget::updateCount()
|
||||
{
|
||||
if (!_db.isOpen()) {
|
||||
return;
|
||||
}
|
||||
QSqlQuery query(_db);
|
||||
if (query.exec("SELECT COUNT(*) FROM random_data") && query.next()) {
|
||||
_countId->setText(query.value(0).toString());
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::addRecord()
|
||||
{
|
||||
if (_nameEdit->displayText().isEmpty() || _textEdit->toPlainText().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!_db.isOpen()) {
|
||||
return;
|
||||
}
|
||||
QSqlQuery query(_db);
|
||||
query.prepare("INSERT INTO public.random_data (name, text) "
|
||||
"VALUES (?, ?)");
|
||||
query.addBindValue(_nameEdit->displayText());
|
||||
query.addBindValue(_textEdit->toPlainText());
|
||||
if (!query.exec()) {
|
||||
return;
|
||||
}
|
||||
_nameEdit->clear();
|
||||
_textEdit->clear();
|
||||
updateCount();
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef WIDGET_H
|
||||
#define WIDGET_H
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSqlDatabase>
|
||||
#include <QTextEdit>
|
||||
#include <QWidget>
|
||||
|
||||
class Widget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Widget(QWidget *parent = nullptr);
|
||||
~Widget();
|
||||
|
||||
private:
|
||||
QSqlDatabase _db;
|
||||
QGridLayout *_grid = nullptr;
|
||||
QLabel *_countId = nullptr;
|
||||
QLabel *_nameLabel = nullptr;
|
||||
QLineEdit *_nameEdit = nullptr;
|
||||
QLabel *_textLabel = nullptr;
|
||||
QTextEdit *_textEdit = nullptr;
|
||||
QPushButton *_addBtn = nullptr;
|
||||
void updateCount();
|
||||
public slots:
|
||||
void init(const QSqlDatabase &);
|
||||
void addRecord();
|
||||
};
|
||||
#endif // WIDGET_H
|
||||
Loading…
Reference in New Issue