Страница 1 из 2
Нужно разрешить редактировать только отдельные поля
Добавлено: 05 сен 2006, 23:25
Tokolist
Здравствуйте эксперты!
Мне нужно разрешить некоторым пользователям редактировать только отдельные поля (разделять таблицу на несколько с связью «один к одному» не подходит, поскольку в будущем планируется разрешать или запрещать редактировать поля из той же таблицы, а пересоздавать таблицы проблематично). Использую TIBQuery в связке с TIBUpdateSQL. Например, разрешено редактировать только поле field3, а есть поля field1, field2, field3 и BLOB-поле notes. Но если в ModifySQL записать
Код: Выделить всё
update "table"
set
"field1" = :"field1",
"field2" = :"field2",
"field3" = :"field3",
"notes" = :notes
where
"id" = :"OLD_id"
получим ошибку. Как решение пришло в голову динамически создавать запрос в событии BeforePost компонента TIBQuery. В запрос добавляются только поля, которые изменялись пользователем.
Код: Выделить всё
ibqQuery: TIBQuery;
ibusUpdateSQL: TIBUpdateSQL;
procedure TfrmForm.ibqQueryBeforePost(DataSet: TDataSet);
function SQLGetUpdatedField(Field: TField; out SQL: string): boolean;
begin
Result := Field.NewValue <> Field.OldValue;
if Result then
SQL := Field.Origin + '=:' + Field.FieldName;
end;
var
TmpStr: string;
begin
ibusUpdateSQL.ModifySQL.Clear;
ibusUpdateSQL.ModifySQL.Add('UPDATE "table" SET ');
if SQLGetUpdatedField(ibqQuery.FieldByName('field1'), TmpStr) then
ibusUpdateSQL.ModifySQL.Add(TmpStr + ', ');
if SQLGetUpdatedField(ibqQuery.FieldByName('field2'), TmpStr) then
ibusUpdateSQL.ModifySQL.Add(TmpStr + ', ');
if SQLGetUpdatedField(ibqQuery.FieldByName('field3'), TmpStr) then
ibusUpdateSQL.ModifySQL.Add(TmpStr + ', ');
ibusUpdateSQL.ModifySQL.Add('"notes" = :notes ');
ibusUpdateSQL.ModifySQL.Add('WHERE "id" = :OLD_id');
end;
Вопросы:
1. Есть ли способ лучше?
2. В BLOB-поле проверка Field.NewValue <> Field.OldValue не работает (поэтому приходится разрешать его редактирование). Как тогда отследить изменялось ли оно?
3. Как можно оптимизировать приведенный код.
Заранее спасибо!
PS. Еще приходит мысль об использовании триггера, в котором проверяется имя пользователя. Но этот вариант тоже не совсем подходит.
Re: Нужно разрешить редактировать только отдельные поля
Добавлено: 06 сен 2006, 05:49
stix-s
slv2 писал(а):
1. Есть ли способ лучше?
Раздать права на модификацию полей по пользователям (по ролям)
Использовать компоненты FIB+
Re: Нужно разрешить редактировать только отдельные поля
Добавлено: 06 сен 2006, 12:52
Merlin
stix-s писал(а):slv2 писал(а):
1. Есть ли способ лучше?
Раздать права на модификацию полей по пользователям (по ролям)
И што? Он это и сделал, после чего стал получать отлуп на препаре запроса.
stix-s писал(а):
Использовать компоненты FIB+
И што буит?
2 slv2 - В принципе твоя идея правильная (я, кстати, не уверен насчёт того, что IBX позволяет пересобирать модифай-запросы при открытом селектном, но чём чёрт не шутит, у меня он уж очень древний, может что там и сдвинулось с мёртвой точки). А конкретная реализация - ну она очень разной может быть и утверждать что в каком случае оптимальней - это занятие или для рискового или для очень неленивого, всё ж перепробовать надо. Насчёт блобов - выдели алгоритмически их редактирование в отдельную ветку, проще будет. А может и чтение лучше отдельным запросом. Или фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
Re: Нужно разрешить редактировать только отдельные поля
Добавлено: 06 сен 2006, 13:17
stix-s
Merlin писал(а):stix-s писал(а):
Раздать права на модификацию полей по пользователям (по ролям)
И што? Он это и сделал, после чего стал получать отлуп на препаре запроса.
В свое время пробовал с компонентом TpFIBDataSet - не ругался, просто втихую запрос модифицировал и все.
Merlin писал(а):
stix-s писал(а):
Использовать компоненты FIB+
И што буит?
ничего не будет, те поля которые нельзя модифицировать, останутся неизменными
Re: Нужно разрешить редактировать только отдельные поля
Добавлено: 06 сен 2006, 13:49
Merlin
stix-s писал(а):Merlin писал(а):stix-s писал(а):
Раздать права на модификацию полей по пользователям (по ролям)
И што? Он это и сделал, после чего стал получать отлуп на препаре запроса.
В свое время пробовал с компонентом TpFIBDataSet - не ругался, просто втихую запрос модифицировал и все.
Merlin писал(а):
stix-s писал(а):
Использовать компоненты FIB+
И што буит?
ничего не будет, те поля которые нельзя модифицировать, останутся неизменными
Значит, Бузз берёт на себя работу сервера по проверке прав, а потом ещё и молча выполняет не то, что прошено? Верится с трудом. Хотя,
коммерческий продукт, кто платит деньги, тот и заказывает музыку... Вот по причине наличия такого рода "фич" я на него так и не перебрался. Хотя в данном конкретном случае таки берут серьёзные сомнения о точности наблюдений за FIB+, уж больно некооректное поведение...
Добавлено: 06 сен 2006, 21:45
Tokolist
(я, кстати, не уверен насчёт того, что IBX позволяет пересобирать модифай-запросы при открытом селектном, но чём чёрт не шутит, у меня он уж очень древний, может что там и сдвинулось с мёртвой точки).
Работает. Честное пионерское

. (Delphi 5 Update 1, IBX 5.04)
А конкретная реализация - ну она очень разной может быть и утверждать что в каком случае оптимальней - это занятие или для рискового или для очень неленивого, всё ж перепробовать надо.
Как бы ты решил данную проблему?
Насчёт блобов - выдели алгоритмически их редактирование в отдельную ветку, проще будет.
Ты имеешь ввиду использовать не DB-компонент, а простой контрол и при посте загружать данные из него в поле?
Или фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
В принципе подходит. Но если пользователь начал редактирование, а потом передумал и вернул поле в начальное состояние, то метка все равно ведь останется.
Использовать компоненты FIB+
Хотелось бы все же ограничится использованием IBX.
Добавлено: 06 сен 2006, 22:14
Merlin
slv2 писал(а):
Как бы ты решил данную проблему?
Данная проблема возникает главным образом при заточке интерфейса на редактирование в гридах, через edit-post. Я такой технологией почти не пользуюсь. При редактировании поднимается отдельная форма, запись рефрешится в её не db-aware контролы, сохранение результатов у меня крайне редко идёт только в одну таблицу и даже в одной транзакции. То есть выполняется несколько автономных TIBSQL или TIBQuery.ExecSQL, которые частенько собираются динамически по обстоятельствам. или вызываются TIBStoredProc. После чего в навигационном датасете рефреш. Ты делаешь примерно то же самое вокруг технологии Edit-Post, что тебе в твоей конкретной задаче удобнее - мне отсюда не видно
slv2 писал(а):
Насчёт блобов - выдели алгоритмически их редактирование в отдельную ветку, проще будет.
Ты имеешь ввиду использовать не DB-компонент, а простой контрол и при посте загружать данные из него в поле?
Я вообще с блобами почти не работаю, я тут слабый советчик. А если работаю, то в навигационном запросе их не тащу, а только при активизации формы редактирования, именно от этой записи, ну а там уже всё в моих руках, как хочу, так и верчу.
slv2 писал(а):
Или фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
В принципе подходит. Но если пользователь начал редактирование, а потом передумал и вернул поле в начальное состояние, то метка все равно ведь останется.
Например, оставь в своей технологии редактирование обычных полей как есть, а блоб поднимай в отдельную форму по вызову функции редактирования именно его, с кнопками OK-Cancel, и сохраняй там отдельным запросом. Можно наверное озаботиться параноидальной проверкой содержимого блоба даже при нажатии OK, сравнить его с исходным, я такими делами не занимаюсь - свип ночью с мусором разберётся. Но вообще-то я, как уже говорил, насчёт блобов небольшой спец, у меня там только шаблоны отчётов.
Добавлено: 07 сен 2006, 22:53
Tokolist
При редактировании поднимается отдельная форма, запись рефрешится в её не db-aware контролы, сохранение результатов у меня крайне редко идёт только в одну таблицу и даже в одной транзакции. То есть выполняется несколько автономных TIBSQL или TIBQuery.ExecSQL, которые частенько собираются динамически по обстоятельствам. или вызываются TIBStoredProc.
Круто
фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
Ты был прав. Но борланд об этом уже позаботился ибо можно сделать проверку так
TBlobField(Field).Modified
А я ведь знал об этом свойстве (склероз блин

)
В свое время пробовал с компонентом TpFIBDataSet - не ругался, просто втихую запрос модифицировал и все.
Скачал FIB Plus... Порился в исходниках. Оказалось он делает (помоему по умолчанию) почти такую же проверку как я на предмет изменения полей и динамически генерирует ModifySQL если установлено соответсвующее свойство.
Но работать все равно буду с IBX (все равно его не брошу потомушо он хороший

).
Короче написал я функцию
Код: Выделить всё
{
DataSet - датасет с которым работаем
FieldNames - список полей, разделенных двоеточием которые нужно проверить на предмет изменения (не всегда ведь нужно проверять все поля, например при джоине)
Quote - заключать названия параметров в кавычки?
}
function SQLGetUpdatedFields(DataSet: TDataSet; const FieldNames: string;
Quote: boolean): string;
var
i: Integer;
TmpStr: string;
Fields: TStrings;
function GetUpdatedField(Field: TField; out SQL: string): boolean;
begin
if Field.IsBlob then
Result := TBlobField(Field).Modified
else
Result := Field.NewValue <> Field.OldValue;
if Result then
begin
if Quote then
SQL := Field.Origin + '=:' + '"' + Field.FieldName + '"'
else
SQL := Field.Origin + '=:' + Field.FieldName;
end;
end;
begin
Result := '';
Fields := TStringList.Create;
try
Fields.Text := StringReplace(FieldNames, ':', #13, [rfReplaceAll]);
for i := 0 to Fields.Count - 1 do
begin
if GetUpdatedField(DataSet.FieldByName(Fields[i]), TmpStr) then
begin
if Result <> '' then
Result := Result + ', ' + #13;
Result := Result + TmpStr;
end;
end;
finally
Fields.Free;
end;
end;
Разрешаю использовать на свой страх и риск (GNU GPL и все такое)
Теперь как использовать
Код: Выделить всё
procedure TfrmForm.ibqQueryBeforePost(DataSet: TDataSet);
var
UpdatedFields: string;
begin
UpdatedFields := SQLGetUpdatedFields(ibqQuery,
'field1:field2:field3:notes', true); //Получаем список измененных полей
if UpdatedFields = '' then
begin
ibqQuery.Cancel; //Если пуст отменяем редактирование
Abort; //И аборт (в смысле прекращаем операцию)
end;
with ibusUpdateSQL.ModifySQL do
begin
Clear;
Add('UPDATE "table" SET ');
Text := Text + UpdatedFields;
Add(' WHERE "id" = :"OLD_id"');
end;
end;
Спасибо за интересную дискуссию!
Добавлено: 07 сен 2006, 23:41
kdv
Круто
это несколько затратный способ в смысле изменения привычек и чуть большего написания кода, вместо тыканья мышой в проперти. Зато получается совершенно четкий контроль за управлением тем, что происходит в программе (включая управление транзакциями).
Добавлено: 15 сен 2006, 13:40
СанЕк
я такую проблему решал следующи образом
допустиместь таблица
Код: Выделить всё
CREATE TABLE ISDELIJA (
SP3 INTEGER NOT NULL,
NUMCLASS INTEGER DEFAULT 0 NOT NULL,
SIFR VARCHAR(50) COLLATE PXW_CYRL,
UNAME VARCHAR(70) COLLATE PXW_CYRL,
FULLNAME VARCHAR(100) COLLATE PXW_CYRL,
OPTCENA NUMERIC(10,2) DEFAULT 0 NOT NULL,
NCP NUMERIC(10,2) DEFAULT 0 NOT NULL,
CENA NUMERIC(10,2) DEFAULT 0 NOT NULL
);
на нее есть триггер
Код: Выделить всё
CREATE TRIGGER ISDELIJA_BU0 FOR ISDELIJA
ACTIVE BEFORE UPDATE POSITION 0
AS
DECLARE VARIABLE FieldName varchar(31);
begin
for SELECT a.RDB$FIELD_NAME FROM RDB$RELATION_FIELDS A Where (a.RDB$RELATION_NAME='ISDELIJA')
and(A.RDB$FIELD_NAME not in (SELECT B.RDB$FIELD_NAME FROM RDB$USER_PRIVILEGES B where
(B.RDB$PRIVILEGE='U')and(B.RDB$RELATION_NAME='ISDELIJA')And(B.RDB$USER=Current_user)and(not B.RDB$FIELD_NAME is Null)))
Into :fieldname do begin
If (:fieldname='SIFR') then new.SIFR=old.SIFR; else
If (:fieldname='UNAME') then new.UNAME=old.UNAME; else
If (:fieldname='FULLNAME') then new.FULLNAME=old.FULLNAME; else
If (:fieldname='OPTCENA') then new.OPTCENA=old.OPTCENA; else
If (:fieldname='NCP') then new.NCP=old.NCP; else
If (:fieldname='NUMCLASS') then new.NUMCLASS=old.NUMCLASS;else
If (:fieldname='OPTCENA') then new.OPTCENA=old.OPTCENA;
end
end
таким образом при изменении записи изменяються только те что дозволены, а те что не разрешены остаються старыми. права раздаються стандартными способами IbExpert скажем
Добавлено: 23 окт 2006, 22:13
Tokolist
Решил заглянуть на форум...
2 СанЕк
У меня Ваш способ почему-то не сработал. Мне кажется, что ошибка возникает еще на препаре...
Кроме того:
- будет ли Ваш способ работать с блоб-полями?
- здесь также необходимо добавить проверку по ролях.
- несколько трудновато написать такой триггер для каждой таблицы (вот бы генератор).
Я решил все-таки контролировать этот процесс на стороне клиента.
Мне также не подошел приведенный мной способ, поскольку необходимо везде обрабатывать событие BeforePost.
По этому было решено создать наследника TIBQuery, который бы делал всю работу автоматически. От меня требуется только кинуть его на форму и заполнить необходимые свойства. Вот уже некоторое время работаю с этим компонентом и ошибок замечено не было (но человек не идеален

).
А так как я НЕ жадный

, то даю прямую ссылку на скачивание (с исходником).
http://pelesh.pochta.ru/pibqueryex.zip (14 КБ)
Буду благодарен за любые комментарии и критику.
Принцип работы следующий. Обрабатывается виртуальный метод DataEvent на предмет изменения полей. Все измененные поля запоминаются в TList и перед постом эти данные используются для генерации ModifySQL компонента TIBUpdateSQLW.
Добавлено: 24 окт 2006, 07:48
CyberMax
2
slv2. Зачем вы так операцию на глазах делаете? Список измененных полей получают вот так:
Код: Выделить всё
for I := 0 to Fields.Count - 1 do
if Fields[I].NewValue <> Fields[I].OldValue then
...
Плюс: если поле изменили, а потом вернули старое значение, датасет будет считаться немодифицированным, а у вас - модифицированным.
Добавлено: 24 окт 2006, 22:05
Tokolist
2 CyberMax
Зачем вы так операцию на глазах делаете? Список измененных полей получают вот так:
Код: Выделить всё
for I := 0 to Fields.Count - 1 do
if Fields[I].NewValue <> Fields[I].OldValue then
...
Я знаю. Я приблизительно так и делал
Код: Выделить всё
function SQLGetUpdatedField(Field: TField; out SQL: string): boolean;
begin
Result := Field.NewValue <> Field.OldValue;
if Result then
SQL := Field.Origin + '=:' + Field.FieldName;
end;
Проблема в том, что это не работает с блоб-полями.
Плюс: если поле изменили, а потом вернули старое значение, датасет будет считаться немодифицированным, а у вас - модифицированным.
Я тоже так сначала думал (что это плохо):
Но если пользователь начал редактирование, а потом передумал и вернул поле в начальное состояние, то метка все равно ведь останется.
Но, если используется, например TDBNavigator и пользователь начал редактирование (одного поля), то пост-кнопка делается доступной и будет такой даже если пользователь вернул поле в начальное состояние (датасет переходит в состояние Edit).
Теперь предположим, что пользователь нажимает эту кнопку (или просто переходит к следующей записи).
Но ведь проверка покажет что поле не изменялось. Мы получаем не правильно сгенерированный запрос и соответственно ошибку.
Я это обходил так
Код: Выделить всё
if UpdatedFields = '' then
begin
ibqQuery.Cancel; //Если пуст отменяем редактирование
Abort; //И аборт (в смысле прекращаем операцию)
end;
Но как оказалось работает такое не совсем корректно.
А так мы получаем список полей которые редактировались вообще.
И это правильно ибо нечего пользователем редактировать поля которые редактировать им не разрешено. Кроме того, с блоб-полями никаких проблем.
Добавлено: 25 окт 2006, 14:25
WildSery
slv2 писал(а):А так мы получаем список полей которые редактировались вообще.
И это правильно ибо нечего пользователем редактировать поля которые редактировать им не разрешено.
Чушь, по-моему. Сначала открываем всё, а потом начинаем собирать статистику, куда пользователь полез, куда хотел полезть, куда по ошибке залез...
Может, поля недоступные для конкретного пользователя, делать ReadOnly, и не проверять "куда он хотел, но не полез"?
Добавлено: 25 окт 2006, 15:39
СанЕк
мой способ действительно не самый лучший, я использовал его лишь однажды, и без blob полей все работает до сих пор, в случае необходимости буду иметь ввиду все сказанное выше.
Добавлено: 25 окт 2006, 21:39
Tokolist
2 WildSery
Чушь, по-моему. Сначала открываем всё, а потом начинаем собирать статистику, куда пользователь полез, куда хотел полезть, куда по ошибке залез...
Пользователь, который полез не туда, получит отлуп и уже больше туда не полезет (типа не влезай - убьет

).
Может, поля недоступные для конкретного пользователя, делать ReadOnly, и не проверять "куда он хотел, но не полез"?
Поверьте, я над этим вопросом думал не один час...
...Разве что триггер, предложенный
СанЕк, переделать в запрос, чуточку модифицировать и добавить проверку по ролях. Потом при каждом открытии датасета через такой запрос получать список доступных полей и соответственно выставлять CanModify. Но зачем?
А если в следующих версиях сервера чего-нибудь изменится в плане хранения прав доступа?
Добавлено: 26 окт 2006, 11:55
WildSery
slv2 писал(а):А если в следующих версиях сервера чего-нибудь изменится в плане хранения прав доступа?
Тогда надо будет поменять только этот запрос.
Добавлено: 26 окт 2006, 13:53
Karp
slv2 писал(а):
Поверьте, я над этим вопросом думал не один час...
А если в следующих версиях сервера чего-нибудь изменится в плане хранения прав доступа?
Вот простое решение:
1) как и говорил Merlin редактировать не в гриде, а в отдельной форме;
2) завести табличку с правами для юзеров;
3) если есть права на редактирование поля, то соответствующий контрол доступен для ввода/изменения, если нет - то только просмотр значения.
права давать в виде набора битов, а на клиенте их проверять и делать доступными или недоступными нужные кнопки, контролы "и что у них есть ещё там" (с)

Добавлено: 27 окт 2006, 00:11
Tokolist
WildSery писал(а):Тогда надо будет поменять только этот запрос.
Согласен.
Karp писал(а):2) завести табличку с правами для юзеров;
3) если есть права на редактирование поля, то соответствующий контрол доступен для ввода/изменения, если нет - то только просмотр значения.
права давать в виде набора битов, а на клиенте их проверять и делать доступными или недоступными нужные кнопки, контролы "и что у них есть ещё там" (с)
Если я вас правильно понял то...
Плохо это с точки зрения безопасности. Всегда найдется продвинутый пользователь который вместо ввода данных наберет что-то типа UPDATE ... SET ... Сам когда-то обходил ограничения клиента таким образом. Но, слава богу, таких продвинутых пользователей у меня нет.
Или вы предлагаете раздавать гранты и еще использовать дополнительно табличку с правами. Тогда это накладно.
И потом для чего изобретать велосипед. Разработчики сервера уже позаботились об ограничении прав доступа.
Хотя идея с доступностью/недоступностью мне нравится. Будет время - поработаю над этим.
P.S. Я использую не грид, а едиты. (хотя какая разница)
Добавлено: 27 окт 2006, 10:49
Karp
slv2 писал(а):Плохо это с точки зрения безопасности. Всегда найдется продвинутый пользователь который вместо ввода данных наберет что-то типа UPDATE ... SET ...
а что, у вас юзера сами запросы пишут? оригинально
они и знать не должны про это, а если есть кто-то продвинутый, то у нас он не сможет этого сделать, т.к. не знает имени сервера и пути к базе (это для особо шибких - которые знают про эксперт или др. тулзы)
Или вы предлагаете раздавать гранты и еще использовать дополнительно табличку с правами. Тогда это накладно.
нет, гранты ролям на таблицы, а не на поля.
по битовой маске (считав права из базы) на клиенте проверять прва на поля - и enabled=ture на разрешённые поля
Хотя я считаю права на поля избыточностью
у нас каждое приложение имеет определённый набор раздаваемых юзерам прав, например: -ведение справочников; -редактирование записи; -доступ к отчётам (к тем, каие нужны или ко всем) ну и т.п.
и есть, например, права на изменение статуса документа - это из меню или по кнопульке - т.е. доступ только к одному полю
а при редактировании у нас и так есть часть полей, которые нельзя менять никому
